forked from MirrorHub/synapse
Support non-OpenID compliant user info endpoints (#14753)
OpenID specifies the format of the user info endpoint and some OAuth 2.0 IdPs do not follow it, e.g. NextCloud and Twitter. This adds subject_template and picture_template options to the default mapping provider for more flexibility in matching those user info responses.
This commit is contained in:
parent
db1cfe9c80
commit
906dfaa2cf
3 changed files with 42 additions and 8 deletions
1
changelog.d/14753.feature
Normal file
1
changelog.d/14753.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Support non-OpenID compliant userinfo claims for subject and picture.
|
|
@ -3098,10 +3098,26 @@ Options for each entry include:
|
||||||
|
|
||||||
For the default provider, the following settings are available:
|
For the default provider, the following settings are available:
|
||||||
|
|
||||||
|
* `subject_template`: Jinja2 template for a unique identifier for the user.
|
||||||
|
Defaults to `{{ user.sub }}`, which OpenID Connect compliant providers should provide.
|
||||||
|
|
||||||
|
This replaces and overrides `subject_claim`.
|
||||||
|
|
||||||
* `subject_claim`: name of the claim containing a unique identifier
|
* `subject_claim`: name of the claim containing a unique identifier
|
||||||
for the user. Defaults to 'sub', which OpenID Connect
|
for the user. Defaults to 'sub', which OpenID Connect
|
||||||
compliant providers should provide.
|
compliant providers should provide.
|
||||||
|
|
||||||
|
*Deprecated in Synapse v1.75.0.*
|
||||||
|
|
||||||
|
* `picture_template`: Jinja2 template for an url for the user's profile picture.
|
||||||
|
Defaults to `{{ user.picture }}`, which OpenID Connect compliant providers should
|
||||||
|
provide and has to refer to a direct image file such as PNG, JPEG, or GIF image file.
|
||||||
|
|
||||||
|
This replaces and overrides `picture_claim`.
|
||||||
|
|
||||||
|
Currently only supported in monolithic (single-process) server configurations
|
||||||
|
where the media repository runs within the Synapse process.
|
||||||
|
|
||||||
* `picture_claim`: name of the claim containing an url for the user's profile picture.
|
* `picture_claim`: name of the claim containing an url for the user's profile picture.
|
||||||
Defaults to 'picture', which OpenID Connect compliant providers should provide
|
Defaults to 'picture', which OpenID Connect compliant providers should provide
|
||||||
and has to refer to a direct image file such as PNG, JPEG, or GIF image file.
|
and has to refer to a direct image file such as PNG, JPEG, or GIF image file.
|
||||||
|
@ -3109,6 +3125,8 @@ Options for each entry include:
|
||||||
Currently only supported in monolithic (single-process) server configurations
|
Currently only supported in monolithic (single-process) server configurations
|
||||||
where the media repository runs within the Synapse process.
|
where the media repository runs within the Synapse process.
|
||||||
|
|
||||||
|
*Deprecated in Synapse v1.75.0.*
|
||||||
|
|
||||||
* `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
|
||||||
own username (see the documentation for the `sso_auth_account_details.html`
|
own username (see the documentation for the `sso_auth_account_details.html`
|
||||||
|
|
|
@ -1520,8 +1520,8 @@ env.filters.update(
|
||||||
|
|
||||||
@attr.s(slots=True, frozen=True, auto_attribs=True)
|
@attr.s(slots=True, frozen=True, auto_attribs=True)
|
||||||
class JinjaOidcMappingConfig:
|
class JinjaOidcMappingConfig:
|
||||||
subject_claim: str
|
subject_template: Template
|
||||||
picture_claim: str
|
picture_template: Template
|
||||||
localpart_template: Optional[Template]
|
localpart_template: Optional[Template]
|
||||||
display_name_template: Optional[Template]
|
display_name_template: Optional[Template]
|
||||||
email_template: Optional[Template]
|
email_template: Optional[Template]
|
||||||
|
@ -1540,8 +1540,23 @@ class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_config(config: dict) -> JinjaOidcMappingConfig:
|
def parse_config(config: dict) -> JinjaOidcMappingConfig:
|
||||||
subject_claim = config.get("subject_claim", "sub")
|
def parse_template_config_with_claim(
|
||||||
picture_claim = config.get("picture_claim", "picture")
|
option_name: str, default_claim: str
|
||||||
|
) -> Template:
|
||||||
|
template_name = f"{option_name}_template"
|
||||||
|
template = config.get(template_name)
|
||||||
|
if not template:
|
||||||
|
# Convert the legacy subject_claim into a template.
|
||||||
|
claim = config.get(f"{option_name}_claim", default_claim)
|
||||||
|
template = "{{ user.%s }}" % (claim,)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return env.from_string(template)
|
||||||
|
except Exception as e:
|
||||||
|
raise ConfigError("invalid jinja template", path=[template_name]) from e
|
||||||
|
|
||||||
|
subject_template = parse_template_config_with_claim("subject", "sub")
|
||||||
|
picture_template = parse_template_config_with_claim("picture", "picture")
|
||||||
|
|
||||||
def parse_template_config(option_name: str) -> Optional[Template]:
|
def parse_template_config(option_name: str) -> Optional[Template]:
|
||||||
if option_name not in config:
|
if option_name not in config:
|
||||||
|
@ -1574,8 +1589,8 @@ class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]):
|
||||||
raise ConfigError("must be a bool", path=["confirm_localpart"])
|
raise ConfigError("must be a bool", path=["confirm_localpart"])
|
||||||
|
|
||||||
return JinjaOidcMappingConfig(
|
return JinjaOidcMappingConfig(
|
||||||
subject_claim=subject_claim,
|
subject_template=subject_template,
|
||||||
picture_claim=picture_claim,
|
picture_template=picture_template,
|
||||||
localpart_template=localpart_template,
|
localpart_template=localpart_template,
|
||||||
display_name_template=display_name_template,
|
display_name_template=display_name_template,
|
||||||
email_template=email_template,
|
email_template=email_template,
|
||||||
|
@ -1584,7 +1599,7 @@ class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_remote_user_id(self, userinfo: UserInfo) -> str:
|
def get_remote_user_id(self, userinfo: UserInfo) -> str:
|
||||||
return userinfo[self._config.subject_claim]
|
return self._config.subject_template.render(user=userinfo).strip()
|
||||||
|
|
||||||
async def map_user_attributes(
|
async def map_user_attributes(
|
||||||
self, userinfo: UserInfo, token: Token, failures: int
|
self, userinfo: UserInfo, token: Token, failures: int
|
||||||
|
@ -1615,7 +1630,7 @@ class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]):
|
||||||
if email:
|
if email:
|
||||||
emails.append(email)
|
emails.append(email)
|
||||||
|
|
||||||
picture = userinfo.get(self._config.picture_claim)
|
picture = self._config.picture_template.render(user=userinfo).strip()
|
||||||
|
|
||||||
return UserAttributeDict(
|
return UserAttributeDict(
|
||||||
localpart=localpart,
|
localpart=localpart,
|
||||||
|
|
Loading…
Reference in a new issue