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

Improve error checking for OIDC/SAML mapping providers #8774

Merged
merged 11 commits into from
Nov 19, 2020
1 change: 1 addition & 0 deletions changelog.d/8774.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expand tests for OpenID Connect.
6 changes: 4 additions & 2 deletions synapse/handlers/oidc_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -885,10 +885,12 @@ async def _map_userinfo_to_user(
"Retrieved user attributes from user mapping provider: %r", attributes
)

if not attributes["localpart"]:
localpart = attributes["localpart"]
if not localpart:
raise MappingException("localpart is empty")

localpart = map_username_to_mxid_localpart(attributes["localpart"])
Copy link
Member Author

Choose a reason for hiding this comment

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

This got pushed into the mapping provider, which matches SAML and gives mapping providers a bit more control (e.g. the dot-replace vs. hex-encode options that SAML has).

Copy link
Member

Choose a reason for hiding this comment

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

Is that a change in the mapping provider API?

Copy link
Member Author

Choose a reason for hiding this comment

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

The SAML mapping provider (implicitly) must provide a valid localpart. I think this was kind of the assumption with the OIDC one as well, but I'm not sure. I failed to update the documentation to make this explicit for both of them, but I meant to! 😄

Copy link
Member Author

Choose a reason for hiding this comment

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

I pushed some documentation improvements (and added something to UPGRADE.rst). I suspect not many people have their own mapping provider, but I'm not sure.

# Ensure only valid characters are included in the MXID.
localpart = map_username_to_mxid_localpart(localpart)

user_id = UserID(localpart, self.server_name).to_string()
users = await self.store.get_users_by_id_case_insensitive(user_id)
Expand Down
54 changes: 49 additions & 5 deletions tests/handlers/test_oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,18 +705,62 @@ def test_map_userinfo_to_user(self):
def test_map_userinfo_to_existing_user(self):
"""Existing users can log in with OpenID Connect when allow_existing_users is True."""
store = self.hs.get_datastore()
user4 = UserID.from_string("@test_user_4:test")
user = UserID.from_string("@test_user:test")
Copy link
Member Author

Choose a reason for hiding this comment

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

There was no reason to start a 4 here so I simplified.

self.get_success(
store.register_user(user_id=user4.to_string(), password_hash=None)
store.register_user(user_id=user.to_string(), password_hash=None)
)
userinfo = {
"sub": "test4",
"username": "test_user_4",
"sub": "test",
"username": "test_user",
}
token = {}
mxid = self.get_success(
self.handler._map_userinfo_to_user(
userinfo, token, "user-agent", "10.10.10.10"
)
)
self.assertEqual(mxid, "@test_user_4:test")
self.assertEqual(mxid, "@test_user:test")

# Register some non-exact matching cases.
user2 = UserID.from_string("@TEST_user_2:test")
self.get_success(
store.register_user(user_id=user2.to_string(), password_hash=None)
)
user2_caps = UserID.from_string("@test_USER_2:test")
self.get_success(
store.register_user(user_id=user2_caps.to_string(), password_hash=None)
)

# Attempting to login without matching a name exactly is an error.
userinfo = {
"sub": "test2",
"username": "test_user_2",
}
e = self.get_failure(
self.handler._map_userinfo_to_user(
userinfo, token, "user-agent", "10.10.10.10"
),
MappingException,
)
self.assertEqual(
str(e.value),
"Attempted to login as '@test_user_2:test' but it matches more than one user inexactly: ['@TEST_user_2:test', '@test_USER_2:test']",
)

# Logging in when matching a name exactly should work. (Note that this
# essentially means matching an all lowercase name.)
user2 = UserID.from_string("@test_user_2:test")
self.get_success(
store.register_user(user_id=user2.to_string(), password_hash=None)
)

userinfo = {
"sub": "test2",
"username": "test_user_2",
}
mxid = self.get_success(
self.handler._map_userinfo_to_user(
userinfo, token, "user-agent", "10.10.10.10"
)
)
self.assertEqual(mxid, "@test_user_2:test")