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

Commit 906dfaa

Browse files
authored
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.
1 parent db1cfe9 commit 906dfaa

File tree

3 files changed

+42
-8
lines changed

3 files changed

+42
-8
lines changed

changelog.d/14753.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support non-OpenID compliant userinfo claims for subject and picture.

docs/usage/configuration/config_documentation.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3098,17 +3098,35 @@ Options for each entry include:
30983098

30993099
For the default provider, the following settings are available:
31003100

3101+
* `subject_template`: Jinja2 template for a unique identifier for the user.
3102+
Defaults to `{{ user.sub }}`, which OpenID Connect compliant providers should provide.
3103+
3104+
This replaces and overrides `subject_claim`.
3105+
31013106
* `subject_claim`: name of the claim containing a unique identifier
31023107
for the user. Defaults to 'sub', which OpenID Connect
31033108
compliant providers should provide.
31043109

3110+
*Deprecated in Synapse v1.75.0.*
3111+
3112+
* `picture_template`: Jinja2 template for an url for the user's profile picture.
3113+
Defaults to `{{ user.picture }}`, which OpenID Connect compliant providers should
3114+
provide and has to refer to a direct image file such as PNG, JPEG, or GIF image file.
3115+
3116+
This replaces and overrides `picture_claim`.
3117+
3118+
Currently only supported in monolithic (single-process) server configurations
3119+
where the media repository runs within the Synapse process.
3120+
31053121
* `picture_claim`: name of the claim containing an url for the user's profile picture.
31063122
Defaults to 'picture', which OpenID Connect compliant providers should provide
31073123
and has to refer to a direct image file such as PNG, JPEG, or GIF image file.
31083124

31093125
Currently only supported in monolithic (single-process) server configurations
31103126
where the media repository runs within the Synapse process.
31113127

3128+
*Deprecated in Synapse v1.75.0.*
3129+
31123130
* `localpart_template`: Jinja2 template for the localpart of the MXID.
31133131
If this is not set, the user will be prompted to choose their
31143132
own username (see the documentation for the `sso_auth_account_details.html`

synapse/handlers/oidc.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,8 +1520,8 @@ def jinja_finalize(thing: Any) -> Any:
15201520

15211521
@attr.s(slots=True, frozen=True, auto_attribs=True)
15221522
class JinjaOidcMappingConfig:
1523-
subject_claim: str
1524-
picture_claim: str
1523+
subject_template: Template
1524+
picture_template: Template
15251525
localpart_template: Optional[Template]
15261526
display_name_template: Optional[Template]
15271527
email_template: Optional[Template]
@@ -1540,8 +1540,23 @@ def __init__(self, config: JinjaOidcMappingConfig):
15401540

15411541
@staticmethod
15421542
def parse_config(config: dict) -> JinjaOidcMappingConfig:
1543-
subject_claim = config.get("subject_claim", "sub")
1544-
picture_claim = config.get("picture_claim", "picture")
1543+
def parse_template_config_with_claim(
1544+
option_name: str, default_claim: str
1545+
) -> Template:
1546+
template_name = f"{option_name}_template"
1547+
template = config.get(template_name)
1548+
if not template:
1549+
# Convert the legacy subject_claim into a template.
1550+
claim = config.get(f"{option_name}_claim", default_claim)
1551+
template = "{{ user.%s }}" % (claim,)
1552+
1553+
try:
1554+
return env.from_string(template)
1555+
except Exception as e:
1556+
raise ConfigError("invalid jinja template", path=[template_name]) from e
1557+
1558+
subject_template = parse_template_config_with_claim("subject", "sub")
1559+
picture_template = parse_template_config_with_claim("picture", "picture")
15451560

15461561
def parse_template_config(option_name: str) -> Optional[Template]:
15471562
if option_name not in config:
@@ -1574,8 +1589,8 @@ def parse_template_config(option_name: str) -> Optional[Template]:
15741589
raise ConfigError("must be a bool", path=["confirm_localpart"])
15751590

15761591
return JinjaOidcMappingConfig(
1577-
subject_claim=subject_claim,
1578-
picture_claim=picture_claim,
1592+
subject_template=subject_template,
1593+
picture_template=picture_template,
15791594
localpart_template=localpart_template,
15801595
display_name_template=display_name_template,
15811596
email_template=email_template,
@@ -1584,7 +1599,7 @@ def parse_template_config(option_name: str) -> Optional[Template]:
15841599
)
15851600

15861601
def get_remote_user_id(self, userinfo: UserInfo) -> str:
1587-
return userinfo[self._config.subject_claim]
1602+
return self._config.subject_template.render(user=userinfo).strip()
15881603

15891604
async def map_user_attributes(
15901605
self, userinfo: UserInfo, token: Token, failures: int
@@ -1615,7 +1630,7 @@ def render_template_field(template: Optional[Template]) -> Optional[str]:
16151630
if email:
16161631
emails.append(email)
16171632

1618-
picture = userinfo.get(self._config.picture_claim)
1633+
picture = self._config.picture_template.render(user=userinfo).strip()
16191634

16201635
return UserAttributeDict(
16211636
localpart=localpart,

0 commit comments

Comments
 (0)