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

Support non-OpenID compliant user info endpoints #14753

Merged
merged 2 commits into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooi how does this interact with the HTML auto-escaping that Jinja normally does? I think we don't want that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this interact with the HTML auto-escaping that Jinja normally does?

Jinja does not do any auto-escaping by default and the env this uses does not enable autoescaping:

env = Environment(finalize=jinja_finalize)
env.filters.update(
{
"localpart_from_email": _localpart_from_email_filter,
}
)

I tested manually too:

>>> from synapse.handlers.oidc import env
>>> template = env.from_string("{{ user.sub }}")
>>> template.render(user={"sub": "<html>"})
'<html>'

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