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

Commit

Permalink
Refactor SsoHandler.get_mxid_from_sso (#8900)
Browse files Browse the repository at this point in the history
* Factor out _call_attribute_mapper and _register_mapped_user

This is mostly an attempt to simplify `get_mxid_from_sso`.

* Move mapping_lock down into SsoHandler.
  • Loading branch information
richvdh authored Dec 10, 2020
1 parent 1821f7c commit c64002e
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 28 deletions.
1 change: 1 addition & 0 deletions changelog.d/8900.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for allowing users to pick their own user ID during a single-sign-on login.
21 changes: 8 additions & 13 deletions synapse/handlers/saml_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
map_username_to_mxid_localpart,
mxid_localpart_allowed_characters,
)
from synapse.util.async_helpers import Linearizer
from synapse.util.iterutils import chunk_seq

if TYPE_CHECKING:
Expand Down Expand Up @@ -81,9 +80,6 @@ def __init__(self, hs: "HomeServer"):
# a map from saml session id to Saml2SessionData object
self._outstanding_requests_dict = {} # type: Dict[str, Saml2SessionData]

# a lock on the mappings
self._mapping_lock = Linearizer(name="saml_mapping", clock=self.clock)

self._sso_handler = hs.get_sso_handler()

def handle_redirect_request(
Expand Down Expand Up @@ -299,15 +295,14 @@ async def grandfather_existing_users() -> Optional[str]:

return None

with (await self._mapping_lock.queue(self._auth_provider_id)):
return await self._sso_handler.get_mxid_from_sso(
self._auth_provider_id,
remote_user_id,
user_agent,
ip_address,
saml_response_to_remapped_user_attributes,
grandfather_existing_users,
)
return await self._sso_handler.get_mxid_from_sso(
self._auth_provider_id,
remote_user_id,
user_agent,
ip_address,
saml_response_to_remapped_user_attributes,
grandfather_existing_users,
)

def _remote_id_from_saml_response(
self,
Expand Down
57 changes: 42 additions & 15 deletions synapse/handlers/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from synapse.api.errors import RedirectException
from synapse.http.server import respond_with_html
from synapse.types import UserID, contains_invalid_mxid_characters
from synapse.util.async_helpers import Linearizer

if TYPE_CHECKING:
from synapse.server import HomeServer
Expand Down Expand Up @@ -54,6 +55,9 @@ def __init__(self, hs: "HomeServer"):
self._error_template = hs.config.sso_error_template
self._auth_handler = hs.get_auth_handler()

# a lock on the mappings
self._mapping_lock = Linearizer(name="sso_user_mapping", clock=hs.get_clock())

def render_error(
self, request, error: str, error_description: Optional[str] = None
) -> None:
Expand Down Expand Up @@ -172,24 +176,38 @@ async def get_mxid_from_sso(
to an additional page. (e.g. to prompt for more information)
"""
# first of all, check if we already have a mapping for this user
previously_registered_user_id = await self.get_sso_user_by_remote_user_id(
auth_provider_id, remote_user_id,
)
if previously_registered_user_id:
return previously_registered_user_id

# Check for grandfathering of users.
if grandfather_existing_users:
previously_registered_user_id = await grandfather_existing_users()
# 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.
with await self._mapping_lock.queue(auth_provider_id):
# first of all, check if we already have a mapping for this user
previously_registered_user_id = await self.get_sso_user_by_remote_user_id(
auth_provider_id, remote_user_id,
)
if previously_registered_user_id:
# Future logins should also match this user ID.
await self._store.record_user_external_id(
auth_provider_id, remote_user_id, previously_registered_user_id
)
return previously_registered_user_id

# Otherwise, generate a new user.
# Check for grandfathering of users.
if grandfather_existing_users:
previously_registered_user_id = await grandfather_existing_users()
if previously_registered_user_id:
# Future logins should also match this user ID.
await self._store.record_user_external_id(
auth_provider_id, remote_user_id, previously_registered_user_id
)
return previously_registered_user_id

# Otherwise, generate a new user.
attributes = await self._call_attribute_mapper(sso_to_matrix_id_mapper)
user_id = await self._register_mapped_user(
attributes, auth_provider_id, remote_user_id, user_agent, ip_address,
)
return user_id

async def _call_attribute_mapper(
self, sso_to_matrix_id_mapper: Callable[[int], Awaitable[UserAttributes]],
) -> UserAttributes:
"""Call the attribute mapper function in a loop, until we get a unique userid"""
for i in range(self._MAP_USERNAME_RETRIES):
try:
attributes = await sso_to_matrix_id_mapper(i)
Expand Down Expand Up @@ -227,7 +245,16 @@ async def get_mxid_from_sso(
raise MappingException(
"Unable to generate a Matrix ID from the SSO response"
)
return attributes

async def _register_mapped_user(
self,
attributes: UserAttributes,
auth_provider_id: str,
remote_user_id: str,
user_agent: str,
ip_address: str,
) -> str:
# Since the localpart is provided via a potentially untrusted module,
# ensure the MXID is valid before registering.
if contains_invalid_mxid_characters(attributes.localpart):
Expand Down

0 comments on commit c64002e

Please sign in to comment.