Skip to content
Snippets Groups Projects
Unverified Commit 003cc691 authored by Brendan Abolivier's avatar Brendan Abolivier Committed by GitHub
Browse files

Update the SSO username picker template to comply with SIWA guidelines (#12210)

parent 32c828d0
No related branches found
No related tags found
No related merge requests found
Update the SSO username picker template to comply with SIWA guidelines.
...@@ -1947,8 +1947,13 @@ saml2_config: ...@@ -1947,8 +1947,13 @@ saml2_config:
# #
# 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 'sso_auth_account_details.html' in the 'sso' # own username (see the documentation for the
# section of this file). # 'sso_auth_account_details.html' template).
#
# confirm_localpart: Whether to prompt the user to validate (or
# change) the generated localpart (see the documentation for the
# 'sso_auth_account_details.html' template), instead of
# registering the account right away.
# #
# 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.
......
...@@ -176,8 +176,11 @@ Below are the templates Synapse will look for when generating pages related to S ...@@ -176,8 +176,11 @@ Below are the templates Synapse will look for when generating pages related to S
for the brand of the IdP for the brand of the IdP
* `user_attributes`: an object containing details about the user that * `user_attributes`: an object containing details about the user that
we received from the IdP. May have the following attributes: we received from the IdP. May have the following attributes:
* display_name: the user's display_name * `display_name`: the user's display name
* emails: a list of email addresses * `emails`: a list of email addresses
* `localpart`: the local part of the Matrix user ID to register,
if `localpart_template` is set in the mapping provider configuration (empty
string if not)
The template should render a form which submits the following fields: The template should render a form which submits the following fields:
* `username`: the localpart of the user's chosen user id * `username`: the localpart of the user's chosen user id
* `sso_new_user_consent.html`: HTML page allowing the user to consent to the * `sso_new_user_consent.html`: HTML page allowing the user to consent to the
......
...@@ -182,8 +182,13 @@ class OIDCConfig(Config): ...@@ -182,8 +182,13 @@ class OIDCConfig(Config):
# #
# 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 'sso_auth_account_details.html' in the 'sso' # own username (see the documentation for the
# section of this file). # 'sso_auth_account_details.html' template).
#
# confirm_localpart: Whether to prompt the user to validate (or
# change) the generated localpart (see the documentation for the
# 'sso_auth_account_details.html' template), instead of
# registering the account right away.
# #
# 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.
......
...@@ -1228,6 +1228,7 @@ class OidcSessionData: ...@@ -1228,6 +1228,7 @@ class OidcSessionData:
class UserAttributeDict(TypedDict): class UserAttributeDict(TypedDict):
localpart: Optional[str] localpart: Optional[str]
confirm_localpart: bool
display_name: Optional[str] display_name: Optional[str]
emails: List[str] emails: List[str]
...@@ -1316,6 +1317,7 @@ class JinjaOidcMappingConfig: ...@@ -1316,6 +1317,7 @@ class JinjaOidcMappingConfig:
display_name_template: Optional[Template] display_name_template: Optional[Template]
email_template: Optional[Template] email_template: Optional[Template]
extra_attributes: Dict[str, Template] extra_attributes: Dict[str, Template]
confirm_localpart: bool = False
class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]): class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]):
...@@ -1357,12 +1359,17 @@ class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]): ...@@ -1357,12 +1359,17 @@ class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]):
"invalid jinja template", path=["extra_attributes", key] "invalid jinja template", path=["extra_attributes", key]
) from e ) from e
confirm_localpart = config.get("confirm_localpart") or False
if not isinstance(confirm_localpart, bool):
raise ConfigError("must be a bool", path=["confirm_localpart"])
return JinjaOidcMappingConfig( return 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, email_template=email_template,
extra_attributes=extra_attributes, extra_attributes=extra_attributes,
confirm_localpart=confirm_localpart,
) )
def get_remote_user_id(self, userinfo: UserInfo) -> str: def get_remote_user_id(self, userinfo: UserInfo) -> str:
...@@ -1398,7 +1405,10 @@ class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]): ...@@ -1398,7 +1405,10 @@ class JinjaOidcMappingProvider(OidcMappingProvider[JinjaOidcMappingConfig]):
emails.append(email) emails.append(email)
return UserAttributeDict( return UserAttributeDict(
localpart=localpart, display_name=display_name, emails=emails localpart=localpart,
display_name=display_name,
emails=emails,
confirm_localpart=self._config.confirm_localpart,
) )
async def get_extra_attributes(self, userinfo: UserInfo, token: Token) -> JsonDict: async def get_extra_attributes(self, userinfo: UserInfo, token: Token) -> JsonDict:
......
...@@ -132,6 +132,7 @@ class UserAttributes: ...@@ -132,6 +132,7 @@ class UserAttributes:
# if `None`, the mapper has not picked a userid, and the user should be prompted to # if `None`, the mapper has not picked a userid, and the user should be prompted to
# enter one. # enter one.
localpart: Optional[str] localpart: Optional[str]
confirm_localpart: bool = False
display_name: Optional[str] = None display_name: Optional[str] = None
emails: Collection[str] = attr.Factory(list) emails: Collection[str] = attr.Factory(list)
...@@ -561,9 +562,10 @@ class SsoHandler: ...@@ -561,9 +562,10 @@ class SsoHandler:
# Must provide either attributes or session, not both # Must provide either attributes or session, not both
assert (attributes is not None) != (session is not None) assert (attributes is not None) != (session is not None)
if (attributes and attributes.localpart is None) or ( if (
session and session.chosen_localpart is None attributes
): and (attributes.localpart is None or attributes.confirm_localpart is True)
) or (session and session.chosen_localpart is None):
return b"/_synapse/client/pick_username/account_details" return b"/_synapse/client/pick_username/account_details"
elif self._consent_at_registration and not ( elif self._consent_at_registration and not (
session and session.terms_accepted_version session and session.terms_accepted_version
......
...@@ -130,15 +130,15 @@ ...@@ -130,15 +130,15 @@
</head> </head>
<body> <body>
<header> <header>
<h1>Your account is nearly ready</h1> <h1>Choose your user name</h1>
<p>Check your details before creating an account on {{ server_name }}</p> <p>This is required to create your account on {{ server_name }}, and you can't change this later.</p>
</header> </header>
<main> <main>
<form method="post" class="form__input" id="form"> <form method="post" class="form__input" id="form">
<div class="username_input" id="username_input"> <div class="username_input" id="username_input">
<label for="field-username">Username</label> <label for="field-username">Username</label>
<div class="prefix">@</div> <div class="prefix">@</div>
<input type="text" name="username" id="field-username" autofocus> <input type="text" name="username" id="field-username" value="{{ user_attributes.localpart }}" autofocus>
<div class="postfix">:{{ server_name }}</div> <div class="postfix">:{{ server_name }}</div>
</div> </div>
<output for="username_input" id="field-username-output"></output> <output for="username_input" id="field-username-output"></output>
......
...@@ -92,12 +92,20 @@ class AccountDetailsResource(DirectServeHtmlResource): ...@@ -92,12 +92,20 @@ class AccountDetailsResource(DirectServeHtmlResource):
self._sso_handler.render_error(request, "bad_session", e.msg, code=e.code) self._sso_handler.render_error(request, "bad_session", e.msg, code=e.code)
return return
# The configuration might mandate going through this step to validate an
# automatically generated localpart, so session.chosen_localpart might already
# be set.
localpart = ""
if session.chosen_localpart is not None:
localpart = session.chosen_localpart
idp_id = session.auth_provider_id idp_id = session.auth_provider_id
template_params = { template_params = {
"idp": self._sso_handler.get_identity_providers()[idp_id], "idp": self._sso_handler.get_identity_providers()[idp_id],
"user_attributes": { "user_attributes": {
"display_name": session.display_name, "display_name": session.display_name,
"emails": session.emails, "emails": session.emails,
"localpart": localpart,
}, },
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment