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

Improve styling and wording of SSO redirect confirm template #9272

Merged
merged 5 commits into from
Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
14 changes: 13 additions & 1 deletion docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1971,7 +1971,8 @@ sso:
# * HTML page for a confirmation step before redirecting back to the client
# with the login token: 'sso_redirect_confirm.html'.
#
# When rendering, this template is given three variables:
# When rendering, this template is given the following variables:
#
# * redirect_url: the URL the user is about to be redirected to. Needs
# manual escaping (see
# https://jinja.palletsprojects.com/en/2.11.x/templates/#html-escaping).
Expand All @@ -1984,6 +1985,17 @@ sso:
#
# * server_name: the homeserver's name.
#
# * new_user: a boolean indicating whether this is the user's first time
# logging in.
#
# * user_id: the user's matrix ID.
#
# * user_profile.avatar_url: an MXC URI for the user's avatar, if any.
# None if the user has not set an avatar.
#
# * user_profile.display_name: the user's display name. None if the user
# has not set a display name.
#
# * HTML page which notifies the user that they are authenticating to confirm
# an operation on their account during the user interactive authentication
# process: 'sso_auth_confirm.html'.
Expand Down
14 changes: 13 additions & 1 deletion synapse/config/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ def generate_config_section(self, **kwargs):
# * HTML page for a confirmation step before redirecting back to the client
# with the login token: 'sso_redirect_confirm.html'.
#
# When rendering, this template is given three variables:
# When rendering, this template is given the following variables:
#
# * redirect_url: the URL the user is about to be redirected to. Needs
# manual escaping (see
# https://jinja.palletsprojects.com/en/2.11.x/templates/#html-escaping).
Expand All @@ -140,6 +141,17 @@ def generate_config_section(self, **kwargs):
#
# * server_name: the homeserver's name.
#
# * new_user: a boolean indicating whether this is the user's first time
# logging in.
#
# * user_id: the user's matrix ID.
#
# * user_profile.avatar_url: an MXC URI for the user's avatar, if any.
# None if the user has not set an avatar.
#
# * user_profile.display_name: the user's display name. None if the user
# has not set a display name.
#
# * HTML page which notifies the user that they are authenticating to confirm
# an operation on their account during the user interactive authentication
# process: 'sso_auth_confirm.html'.
Expand Down
24 changes: 23 additions & 1 deletion synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
from synapse.logging.context import defer_to_thread
from synapse.metrics.background_process_metrics import run_as_background_process
from synapse.module_api import ModuleApi
from synapse.storage.roommember import ProfileInfo
from synapse.types import JsonDict, Requester, UserID
from synapse.util import stringutils as stringutils
from synapse.util.async_helpers import maybe_awaitable
Expand Down Expand Up @@ -1396,6 +1397,7 @@ async def complete_sso_login(
request: Request,
client_redirect_url: str,
extra_attributes: Optional[JsonDict] = None,
new_user: bool = False,
):
"""Having figured out a mxid for this user, complete the HTTP request

Expand All @@ -1406,6 +1408,8 @@ async def complete_sso_login(
process.
extra_attributes: Extra attributes which will be passed to the client
during successful login. Must be JSON serializable.
new_user: True if we should use wording appropriate to a user who has just
registered.
"""
# If the account has been deactivated, do not proceed with the login
# flow.
Expand All @@ -1414,8 +1418,17 @@ async def complete_sso_login(
respond_with_html(request, 403, self._sso_account_deactivated_template)
return

profile = await self.store.get_profileinfo(
UserID.from_string(registered_user_id).localpart
)

self._complete_sso_login(
registered_user_id, request, client_redirect_url, extra_attributes
registered_user_id,
request,
client_redirect_url,
extra_attributes,
new_user=new_user,
user_profile_data=profile,
)

def _complete_sso_login(
Expand All @@ -1424,12 +1437,18 @@ def _complete_sso_login(
request: Request,
client_redirect_url: str,
extra_attributes: Optional[JsonDict] = None,
new_user: bool = False,
user_profile_data: Optional[ProfileInfo] = None,
):
"""
The synchronous portion of complete_sso_login.

This exists purely for backwards compatibility of synapse.module_api.ModuleApi.
"""

if user_profile_data is None:
user_profile_data = ProfileInfo(None, None)

# Store any extra attributes which will be passed in the login response.
# Note that this is per-user so it may overwrite a previous value, this
# is considered OK since the newest SSO attributes should be most valid.
Expand Down Expand Up @@ -1467,6 +1486,9 @@ def _complete_sso_login(
display_url=redirect_url_no_params,
redirect_url=redirect_url,
server_name=self._server_name,
new_user=new_user,
user_id=registered_user_id,
user_profile=user_profile_data,
)
respond_with_html(request, 200, html)

Expand Down
10 changes: 9 additions & 1 deletion synapse/handlers/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ async def complete_sso_login_request(
to an additional page. (e.g. to prompt for more information)

"""
new_user = False

# grab a lock while we try to find a mapping for this user. This seems...
# optimistic, especially for implementations that end up redirecting to
# interstitial pages.
Expand Down Expand Up @@ -431,9 +433,14 @@ async def complete_sso_login_request(
get_request_user_agent(request),
request.getClientIP(),
)
new_user = True

await self._auth_handler.complete_sso_login(
user_id, request, client_redirect_url, extra_login_attributes
user_id,
request,
client_redirect_url,
extra_login_attributes,
new_user=new_user,
)

async def _call_attribute_mapper(
Expand Down Expand Up @@ -778,6 +785,7 @@ async def register_sso_user(self, request: Request, session_id: str) -> None:
request,
session.client_redirect_url,
session.extra_login_attributes,
new_user=True,
)

def _expire_old_sessions(self):
Expand Down
10 changes: 8 additions & 2 deletions synapse/module_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,11 @@ def complete_sso_login(
)

async def complete_sso_login_async(
self, registered_user_id: str, request: SynapseRequest, client_redirect_url: str
self,
registered_user_id: str,
request: SynapseRequest,
client_redirect_url: str,
new_user: bool = False,
Copy link
Member

Choose a reason for hiding this comment

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

Does it make sense to add this to the complete_sso_login version as well? I'm not sure what even calls these anymore.

Copy link
Member Author

Choose a reason for hiding this comment

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

given it's deprecated, I'm not sure there's any value in updating it. Any modules which want to modify the value of new_user should call the async version.

):
"""Complete a SSO login by redirecting the user to a page to confirm whether they
want their access token sent to `client_redirect_url`, or redirect them to that
Expand All @@ -291,9 +295,11 @@ async def complete_sso_login_async(
request: The request to respond to.
client_redirect_url: The URL to which to offer to redirect the user (or to
redirect them directly if whitelisted).
new_user: set to true to use wording for the consent appropriate to a user
who has just registered.
"""
await self._auth_handler.complete_sso_login(
registered_user_id, request, client_redirect_url,
registered_user_id, request, client_redirect_url, new_user=new_user
)

@defer.inlineCallbacks
Expand Down
8 changes: 4 additions & 4 deletions tests/handlers/test_cas.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def test_map_cas_user_to_user(self):

# check that the auth handler got called as expected
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user:test", request, "redirect_uri", None
"@test_user:test", request, "redirect_uri", None, new_user=True
)

def test_map_cas_user_to_existing_user(self):
Expand All @@ -85,7 +85,7 @@ def test_map_cas_user_to_existing_user(self):

# check that the auth handler got called as expected
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user:test", request, "redirect_uri", None
"@test_user:test", request, "redirect_uri", None, new_user=False
)

# Subsequent calls should map to the same mxid.
Expand All @@ -94,7 +94,7 @@ def test_map_cas_user_to_existing_user(self):
self.handler._handle_cas_response(request, cas_response, "redirect_uri", "")
)
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user:test", request, "redirect_uri", None
"@test_user:test", request, "redirect_uri", None, new_user=False
)

def test_map_cas_user_to_invalid_localpart(self):
Expand All @@ -112,7 +112,7 @@ def test_map_cas_user_to_invalid_localpart(self):

# check that the auth handler got called as expected
auth_handler.complete_sso_login.assert_called_once_with(
"@f=c3=b6=c3=b6:test", request, "redirect_uri", None
"@f=c3=b6=c3=b6:test", request, "redirect_uri", None, new_user=True
)


Expand Down
24 changes: 14 additions & 10 deletions tests/handlers/test_oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ def test_callback(self):
self.get_success(self.handler.handle_oidc_callback(request))

auth_handler.complete_sso_login.assert_called_once_with(
expected_user_id, request, client_redirect_url, None,
expected_user_id, request, client_redirect_url, None, new_user=True
)
self.provider._exchange_code.assert_called_once_with(code)
self.provider._parse_id_token.assert_called_once_with(token, nonce=nonce)
Expand Down Expand Up @@ -450,7 +450,7 @@ def test_callback(self):
self.get_success(self.handler.handle_oidc_callback(request))

auth_handler.complete_sso_login.assert_called_once_with(
expected_user_id, request, client_redirect_url, None,
expected_user_id, request, client_redirect_url, None, new_user=False
)
self.provider._exchange_code.assert_called_once_with(code)
self.provider._parse_id_token.assert_not_called()
Expand Down Expand Up @@ -623,7 +623,11 @@ def test_extra_attributes(self):
self.get_success(self.handler.handle_oidc_callback(request))

auth_handler.complete_sso_login.assert_called_once_with(
"@foo:test", request, client_redirect_url, {"phone": "1234567"},
"@foo:test",
request,
client_redirect_url,
{"phone": "1234567"},
new_user=True,
)

def test_map_userinfo_to_user(self):
Expand All @@ -637,7 +641,7 @@ def test_map_userinfo_to_user(self):
}
self.get_success(_make_callback_with_userinfo(self.hs, userinfo))
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user:test", ANY, ANY, None,
"@test_user:test", ANY, ANY, None, new_user=True
)
auth_handler.complete_sso_login.reset_mock()

Expand All @@ -648,7 +652,7 @@ def test_map_userinfo_to_user(self):
}
self.get_success(_make_callback_with_userinfo(self.hs, userinfo))
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user_2:test", ANY, ANY, None,
"@test_user_2:test", ANY, ANY, None, new_user=True
)
auth_handler.complete_sso_login.reset_mock()

Expand Down Expand Up @@ -685,14 +689,14 @@ def test_map_userinfo_to_existing_user(self):
}
self.get_success(_make_callback_with_userinfo(self.hs, userinfo))
auth_handler.complete_sso_login.assert_called_once_with(
user.to_string(), ANY, ANY, None,
user.to_string(), ANY, ANY, None, new_user=False
)
auth_handler.complete_sso_login.reset_mock()

# Subsequent calls should map to the same mxid.
self.get_success(_make_callback_with_userinfo(self.hs, userinfo))
auth_handler.complete_sso_login.assert_called_once_with(
user.to_string(), ANY, ANY, None,
user.to_string(), ANY, ANY, None, new_user=False
)
auth_handler.complete_sso_login.reset_mock()

Expand All @@ -707,7 +711,7 @@ def test_map_userinfo_to_existing_user(self):
}
self.get_success(_make_callback_with_userinfo(self.hs, userinfo))
auth_handler.complete_sso_login.assert_called_once_with(
user.to_string(), ANY, ANY, None,
user.to_string(), ANY, ANY, None, new_user=False
)
auth_handler.complete_sso_login.reset_mock()

Expand Down Expand Up @@ -743,7 +747,7 @@ def test_map_userinfo_to_existing_user(self):

self.get_success(_make_callback_with_userinfo(self.hs, userinfo))
auth_handler.complete_sso_login.assert_called_once_with(
"@TEST_USER_2:test", ANY, ANY, None,
"@TEST_USER_2:test", ANY, ANY, None, new_user=False
)

def test_map_userinfo_to_invalid_localpart(self):
Expand Down Expand Up @@ -779,7 +783,7 @@ def test_map_userinfo_to_user_retries(self):

# test_user is already taken, so test_user1 gets registered instead.
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user1:test", ANY, ANY, None,
"@test_user1:test", ANY, ANY, None, new_user=True
)
auth_handler.complete_sso_login.reset_mock()

Expand Down
8 changes: 4 additions & 4 deletions tests/handlers/test_saml.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def test_map_saml_response_to_user(self):

# check that the auth handler got called as expected
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user:test", request, "redirect_uri", None
"@test_user:test", request, "redirect_uri", None, new_user=True
)

@override_config({"saml2_config": {"grandfathered_mxid_source_attribute": "mxid"}})
Expand All @@ -157,7 +157,7 @@ def test_map_saml_response_to_existing_user(self):

# check that the auth handler got called as expected
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user:test", request, "", None
"@test_user:test", request, "", None, new_user=False
)

# Subsequent calls should map to the same mxid.
Expand All @@ -166,7 +166,7 @@ def test_map_saml_response_to_existing_user(self):
self.handler._handle_authn_response(request, saml_response, "")
)
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user:test", request, "", None
"@test_user:test", request, "", None, new_user=False
)

def test_map_saml_response_to_invalid_localpart(self):
Expand Down Expand Up @@ -214,7 +214,7 @@ def test_map_saml_response_to_user_retries(self):

# test_user is already taken, so test_user1 gets registered instead.
auth_handler.complete_sso_login.assert_called_once_with(
"@test_user1:test", request, "", None
"@test_user1:test", request, "", None, new_user=True
)
auth_handler.complete_sso_login.reset_mock()

Expand Down