mirror of
https://mau.dev/maunium/synapse.git
synced 2024-10-01 01:36:05 -04:00
Support for scraping email addresses from OIDC providers (#9245)
This commit is contained in:
parent
fbd9de6d1f
commit
869667760f
1
changelog.d/9245.feature
Normal file
1
changelog.d/9245.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add support to the OpenID Connect integration for adding the user's email address.
|
@ -1791,9 +1791,9 @@ saml2_config:
|
|||||||
#
|
#
|
||||||
# For the default provider, the following settings are available:
|
# For the default provider, the following settings are available:
|
||||||
#
|
#
|
||||||
# sub: name of the claim containing a unique identifier for the
|
# subject_claim: name of the claim containing a unique identifier
|
||||||
# user. Defaults to 'sub', which OpenID Connect compliant
|
# for the user. Defaults to 'sub', which OpenID Connect
|
||||||
# providers should provide.
|
# compliant providers should provide.
|
||||||
#
|
#
|
||||||
# localpart_template: Jinja2 template for the localpart of the MXID.
|
# localpart_template: Jinja2 template for the localpart of the MXID.
|
||||||
# If this is not set, the user will be prompted to choose their
|
# If this is not set, the user will be prompted to choose their
|
||||||
@ -1802,6 +1802,9 @@ saml2_config:
|
|||||||
# display_name_template: Jinja2 template for the display name to set
|
# display_name_template: Jinja2 template for the display name to set
|
||||||
# on first login. If unset, no displayname will be set.
|
# on first login. If unset, no displayname will be set.
|
||||||
#
|
#
|
||||||
|
# email_template: Jinja2 template for the email address of the user.
|
||||||
|
# If unset, no email address will be added to the account.
|
||||||
|
#
|
||||||
# extra_attributes: a map of Jinja2 templates for extra attributes
|
# extra_attributes: a map of Jinja2 templates for extra attributes
|
||||||
# to send back to the client during login.
|
# to send back to the client during login.
|
||||||
# Note that these are non-standard and clients will ignore them
|
# Note that these are non-standard and clients will ignore them
|
||||||
@ -1837,6 +1840,12 @@ oidc_providers:
|
|||||||
# userinfo_endpoint: "https://accounts.example.com/userinfo"
|
# userinfo_endpoint: "https://accounts.example.com/userinfo"
|
||||||
# jwks_uri: "https://accounts.example.com/.well-known/jwks.json"
|
# jwks_uri: "https://accounts.example.com/.well-known/jwks.json"
|
||||||
# skip_verification: true
|
# skip_verification: true
|
||||||
|
# user_mapping_provider:
|
||||||
|
# config:
|
||||||
|
# subject_claim: "id"
|
||||||
|
# localpart_template: "{ user.login }"
|
||||||
|
# display_name_template: "{ user.name }"
|
||||||
|
# email_template: "{ user.email }"
|
||||||
|
|
||||||
# For use with Keycloak
|
# For use with Keycloak
|
||||||
#
|
#
|
||||||
|
@ -143,9 +143,9 @@ class OIDCConfig(Config):
|
|||||||
#
|
#
|
||||||
# For the default provider, the following settings are available:
|
# For the default provider, the following settings are available:
|
||||||
#
|
#
|
||||||
# sub: name of the claim containing a unique identifier for the
|
# subject_claim: name of the claim containing a unique identifier
|
||||||
# user. Defaults to 'sub', which OpenID Connect compliant
|
# for the user. Defaults to 'sub', which OpenID Connect
|
||||||
# providers should provide.
|
# compliant providers should provide.
|
||||||
#
|
#
|
||||||
# localpart_template: Jinja2 template for the localpart of the MXID.
|
# localpart_template: Jinja2 template for the localpart of the MXID.
|
||||||
# If this is not set, the user will be prompted to choose their
|
# If this is not set, the user will be prompted to choose their
|
||||||
@ -154,6 +154,9 @@ class OIDCConfig(Config):
|
|||||||
# display_name_template: Jinja2 template for the display name to set
|
# display_name_template: Jinja2 template for the display name to set
|
||||||
# on first login. If unset, no displayname will be set.
|
# on first login. If unset, no displayname will be set.
|
||||||
#
|
#
|
||||||
|
# email_template: Jinja2 template for the email address of the user.
|
||||||
|
# If unset, no email address will be added to the account.
|
||||||
|
#
|
||||||
# extra_attributes: a map of Jinja2 templates for extra attributes
|
# extra_attributes: a map of Jinja2 templates for extra attributes
|
||||||
# to send back to the client during login.
|
# to send back to the client during login.
|
||||||
# Note that these are non-standard and clients will ignore them
|
# Note that these are non-standard and clients will ignore them
|
||||||
@ -189,6 +192,12 @@ class OIDCConfig(Config):
|
|||||||
# userinfo_endpoint: "https://accounts.example.com/userinfo"
|
# userinfo_endpoint: "https://accounts.example.com/userinfo"
|
||||||
# jwks_uri: "https://accounts.example.com/.well-known/jwks.json"
|
# jwks_uri: "https://accounts.example.com/.well-known/jwks.json"
|
||||||
# skip_verification: true
|
# skip_verification: true
|
||||||
|
# user_mapping_provider:
|
||||||
|
# config:
|
||||||
|
# subject_claim: "id"
|
||||||
|
# localpart_template: "{{ user.login }}"
|
||||||
|
# display_name_template: "{{ user.name }}"
|
||||||
|
# email_template: "{{ user.email }}"
|
||||||
|
|
||||||
# For use with Keycloak
|
# For use with Keycloak
|
||||||
#
|
#
|
||||||
|
@ -1056,7 +1056,8 @@ class OidcSessionData:
|
|||||||
|
|
||||||
|
|
||||||
UserAttributeDict = TypedDict(
|
UserAttributeDict = TypedDict(
|
||||||
"UserAttributeDict", {"localpart": Optional[str], "display_name": Optional[str]}
|
"UserAttributeDict",
|
||||||
|
{"localpart": Optional[str], "display_name": Optional[str], "emails": List[str]},
|
||||||
)
|
)
|
||||||
C = TypeVar("C")
|
C = TypeVar("C")
|
||||||
|
|
||||||
@ -1135,11 +1136,12 @@ def jinja_finalize(thing):
|
|||||||
env = Environment(finalize=jinja_finalize)
|
env = Environment(finalize=jinja_finalize)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(slots=True, frozen=True)
|
||||||
class JinjaOidcMappingConfig:
|
class JinjaOidcMappingConfig:
|
||||||
subject_claim = attr.ib(type=str)
|
subject_claim = attr.ib(type=str)
|
||||||
localpart_template = attr.ib(type=Optional[Template])
|
localpart_template = attr.ib(type=Optional[Template])
|
||||||
display_name_template = attr.ib(type=Optional[Template])
|
display_name_template = attr.ib(type=Optional[Template])
|
||||||
|
email_template = attr.ib(type=Optional[Template])
|
||||||
extra_attributes = attr.ib(type=Dict[str, Template])
|
extra_attributes = attr.ib(type=Dict[str, Template])
|
||||||
|
|
||||||
|
|
||||||
@ -1156,23 +1158,17 @@ class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]):
|
|||||||
def parse_config(config: dict) -> JinjaOidcMappingConfig:
|
def parse_config(config: dict) -> JinjaOidcMappingConfig:
|
||||||
subject_claim = config.get("subject_claim", "sub")
|
subject_claim = config.get("subject_claim", "sub")
|
||||||
|
|
||||||
localpart_template = None # type: Optional[Template]
|
def parse_template_config(option_name: str) -> Optional[Template]:
|
||||||
if "localpart_template" in config:
|
if option_name not in config:
|
||||||
|
return None
|
||||||
try:
|
try:
|
||||||
localpart_template = env.from_string(config["localpart_template"])
|
return env.from_string(config[option_name])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ConfigError(
|
raise ConfigError("invalid jinja template", path=[option_name]) from e
|
||||||
"invalid jinja template", path=["localpart_template"]
|
|
||||||
) from e
|
|
||||||
|
|
||||||
display_name_template = None # type: Optional[Template]
|
localpart_template = parse_template_config("localpart_template")
|
||||||
if "display_name_template" in config:
|
display_name_template = parse_template_config("display_name_template")
|
||||||
try:
|
email_template = parse_template_config("email_template")
|
||||||
display_name_template = env.from_string(config["display_name_template"])
|
|
||||||
except Exception as e:
|
|
||||||
raise ConfigError(
|
|
||||||
"invalid jinja template", path=["display_name_template"]
|
|
||||||
) from e
|
|
||||||
|
|
||||||
extra_attributes = {} # type Dict[str, Template]
|
extra_attributes = {} # type Dict[str, Template]
|
||||||
if "extra_attributes" in config:
|
if "extra_attributes" in config:
|
||||||
@ -1192,6 +1188,7 @@ class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]):
|
|||||||
subject_claim=subject_claim,
|
subject_claim=subject_claim,
|
||||||
localpart_template=localpart_template,
|
localpart_template=localpart_template,
|
||||||
display_name_template=display_name_template,
|
display_name_template=display_name_template,
|
||||||
|
email_template=email_template,
|
||||||
extra_attributes=extra_attributes,
|
extra_attributes=extra_attributes,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1213,16 +1210,23 @@ class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]):
|
|||||||
# a usable mxid.
|
# a usable mxid.
|
||||||
localpart += str(failures) if failures else ""
|
localpart += str(failures) if failures else ""
|
||||||
|
|
||||||
display_name = None # type: Optional[str]
|
def render_template_field(template: Optional[Template]) -> Optional[str]:
|
||||||
if self._config.display_name_template is not None:
|
if template is None:
|
||||||
display_name = self._config.display_name_template.render(
|
return None
|
||||||
user=userinfo
|
return template.render(user=userinfo).strip()
|
||||||
).strip()
|
|
||||||
|
|
||||||
if display_name == "":
|
display_name = render_template_field(self._config.display_name_template)
|
||||||
display_name = None
|
if display_name == "":
|
||||||
|
display_name = None
|
||||||
|
|
||||||
return UserAttributeDict(localpart=localpart, display_name=display_name)
|
emails = [] # type: List[str]
|
||||||
|
email = render_template_field(self._config.email_template)
|
||||||
|
if email:
|
||||||
|
emails.append(email)
|
||||||
|
|
||||||
|
return UserAttributeDict(
|
||||||
|
localpart=localpart, display_name=display_name, emails=emails
|
||||||
|
)
|
||||||
|
|
||||||
async def get_extra_attributes(self, userinfo: UserInfo, token: Token) -> JsonDict:
|
async def get_extra_attributes(self, userinfo: UserInfo, token: Token) -> JsonDict:
|
||||||
extras = {} # type: Dict[str, str]
|
extras = {} # type: Dict[str, str]
|
||||||
|
Loading…
Reference in New Issue
Block a user