Skip to content

Commit 1f4ae2f

Browse files
Allow only requiring a field be present in an SSO response, rather than specifying a required value (#18454)
1 parent 17e6b32 commit 1f4ae2f

4 files changed

Lines changed: 86 additions & 9 deletions

File tree

changelog.d/18454.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow checking only for the existence of a field in an SSO provider's response, rather than requiring the value(s) to check.

docs/usage/configuration/config_documentation.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3782,17 +3782,23 @@ match particular values in the OIDC userinfo. The requirements can be listed und
37823782
```yaml
37833783
attribute_requirements:
37843784
- attribute: family_name
3785-
value: "Stephensson"
3785+
one_of: ["Stephensson", "Smith"]
37863786
- attribute: groups
37873787
value: "admin"
3788+
# If `value` or `one_of` are not specified, the attribute only needs
3789+
# to exist, regardless of value.
3790+
- attribute: picture
37883791
```
3792+
3793+
`attribute` is a required field, while `value` and `one_of` are optional.
3794+
37893795
All of the listed attributes must match for the login to be permitted. Additional attributes can be added to
37903796
userinfo by expanding the `scopes` section of the OIDC config to retrieve
37913797
additional information from the OIDC provider.
37923798

37933799
If the OIDC claim is a list, then the attribute must match any value in the list.
37943800
Otherwise, it must exactly match the value of the claim. Using the example
3795-
above, the `family_name` claim MUST be "Stephensson", but the `groups`
3801+
above, the `family_name` claim MUST be either "Stephensson" or "Smith", but the `groups`
37963802
claim MUST contain "admin".
37973803

37983804
Example configuration:

synapse/config/sso.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ class SsoAttributeRequirement:
4343
"""Object describing a single requirement for SSO attributes."""
4444

4545
attribute: str
46-
# If neither value nor one_of is given, the attribute must simply exist. This is
47-
# only true for CAS configs which use a different JSON schema than the one below.
46+
# If neither `value` nor `one_of` is given, the attribute must simply exist.
4847
value: Optional[str] = None
4948
one_of: Optional[List[str]] = None
5049

@@ -56,10 +55,6 @@ class SsoAttributeRequirement:
5655
"one_of": {"type": "array", "items": {"type": "string"}},
5756
},
5857
"required": ["attribute"],
59-
"oneOf": [
60-
{"required": ["value"]},
61-
{"required": ["one_of"]},
62-
],
6358
}
6459

6560

tests/handlers/test_oidc.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1453,7 +1453,7 @@ def test_attribute_requirements_contains(self) -> None:
14531453
}
14541454
}
14551455
)
1456-
def test_attribute_requirements_one_of(self) -> None:
1456+
def test_attribute_requirements_one_of_succeeds(self) -> None:
14571457
"""Test that auth succeeds if userinfo attribute has multiple values and CONTAINS required value"""
14581458
# userinfo with "test": ["bar"] attribute should succeed.
14591459
userinfo = {
@@ -1475,6 +1475,81 @@ def test_attribute_requirements_one_of(self) -> None:
14751475
auth_provider_session_id=None,
14761476
)
14771477

1478+
@override_config(
1479+
{
1480+
"oidc_config": {
1481+
**DEFAULT_CONFIG,
1482+
"attribute_requirements": [
1483+
{"attribute": "test", "one_of": ["foo", "bar"]}
1484+
],
1485+
}
1486+
}
1487+
)
1488+
def test_attribute_requirements_one_of_fails(self) -> None:
1489+
"""Test that auth fails if userinfo attribute has multiple values yet
1490+
DOES NOT CONTAIN a required value
1491+
"""
1492+
# userinfo with "test": ["something else"] attribute should fail.
1493+
userinfo = {
1494+
"sub": "tester",
1495+
"username": "tester",
1496+
"test": ["something else"],
1497+
}
1498+
request, _ = self.start_authorization(userinfo)
1499+
self.get_success(self.handler.handle_oidc_callback(request))
1500+
self.complete_sso_login.assert_not_called()
1501+
1502+
@override_config(
1503+
{
1504+
"oidc_config": {
1505+
**DEFAULT_CONFIG,
1506+
"attribute_requirements": [{"attribute": "test"}],
1507+
}
1508+
}
1509+
)
1510+
def test_attribute_requirements_does_not_exist(self) -> None:
1511+
"""OIDC login fails if the required attribute does not exist in the OIDC userinfo response."""
1512+
# userinfo lacking "test" attribute should fail.
1513+
userinfo = {
1514+
"sub": "tester",
1515+
"username": "tester",
1516+
}
1517+
request, _ = self.start_authorization(userinfo)
1518+
self.get_success(self.handler.handle_oidc_callback(request))
1519+
self.complete_sso_login.assert_not_called()
1520+
1521+
@override_config(
1522+
{
1523+
"oidc_config": {
1524+
**DEFAULT_CONFIG,
1525+
"attribute_requirements": [{"attribute": "test"}],
1526+
}
1527+
}
1528+
)
1529+
def test_attribute_requirements_exist(self) -> None:
1530+
"""OIDC login succeeds if the required attribute exist (regardless of value)
1531+
in the OIDC userinfo response.
1532+
"""
1533+
# userinfo with "test" attribute and random value should succeed.
1534+
userinfo = {
1535+
"sub": "tester",
1536+
"username": "tester",
1537+
"test": random_string(5), # value does not matter
1538+
}
1539+
request, _ = self.start_authorization(userinfo)
1540+
self.get_success(self.handler.handle_oidc_callback(request))
1541+
1542+
# check that the auth handler got called as expected
1543+
self.complete_sso_login.assert_called_once_with(
1544+
"@tester:test",
1545+
self.provider.idp_id,
1546+
request,
1547+
ANY,
1548+
None,
1549+
new_user=True,
1550+
auth_provider_session_id=None,
1551+
)
1552+
14781553
@override_config(
14791554
{
14801555
"oidc_config": {

0 commit comments

Comments
 (0)