Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Support non-OpenID compliant user info endpoints (#14753)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
clokep committed Jan 4, 2023
1 parent db1cfe9 commit 906dfaa
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 8 deletions.
1 change: 1 addition & 0 deletions changelog.d/14753.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support non-OpenID compliant userinfo claims for subject and picture.
18 changes: 18 additions & 0 deletions docs/usage/configuration/config_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3098,17 +3098,35 @@ Options for each entry include:

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
for the user. Defaults to 'sub', which OpenID Connect
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.
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.

Currently only supported in monolithic (single-process) server configurations
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.
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`
Expand Down
31 changes: 23 additions & 8 deletions synapse/handlers/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1520,8 +1520,8 @@ def jinja_finalize(thing: Any) -> Any:

@attr.s(slots=True, frozen=True, auto_attribs=True)
class JinjaOidcMappingConfig:
subject_claim: str
picture_claim: str
subject_template: Template
picture_template: Template
localpart_template: Optional[Template]
display_name_template: Optional[Template]
email_template: Optional[Template]
Expand All @@ -1540,8 +1540,23 @@ def __init__(self, config: JinjaOidcMappingConfig):

@staticmethod
def parse_config(config: dict) -> JinjaOidcMappingConfig:
subject_claim = config.get("subject_claim", "sub")
picture_claim = config.get("picture_claim", "picture")
def parse_template_config_with_claim(
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]:
if option_name not in config:
Expand Down Expand Up @@ -1574,8 +1589,8 @@ def parse_template_config(option_name: str) -> Optional[Template]:
raise ConfigError("must be a bool", path=["confirm_localpart"])

return JinjaOidcMappingConfig(
subject_claim=subject_claim,
picture_claim=picture_claim,
subject_template=subject_template,
picture_template=picture_template,
localpart_template=localpart_template,
display_name_template=display_name_template,
email_template=email_template,
Expand All @@ -1584,7 +1599,7 @@ def parse_template_config(option_name: str) -> Optional[Template]:
)

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(
self, userinfo: UserInfo, token: Token, failures: int
Expand Down Expand Up @@ -1615,7 +1630,7 @@ def render_template_field(template: Optional[Template]) -> Optional[str]:
if email:
emails.append(email)

picture = userinfo.get(self._config.picture_claim)
picture = self._config.picture_template.render(user=userinfo).strip()

return UserAttributeDict(
localpart=localpart,
Expand Down

0 comments on commit 906dfaa

Please sign in to comment.