Skip to content

Commit

Permalink
impr: Introduce one_of list for SSO attribute values
Browse files Browse the repository at this point in the history
  • Loading branch information
pizkaz committed Nov 21, 2024
1 parent d83ceeb commit 297c68c
Show file tree
Hide file tree
Showing 8 changed files with 37 additions and 16 deletions.
2 changes: 1 addition & 1 deletion changelog.d/17949.feature
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Add functionality to be able to use multiple values in SSO featute `attribute_requirements` via comma seperated list.
Add functionality to be able to use multiple values in SSO feature `attribute_requirements`.
10 changes: 6 additions & 4 deletions docs/usage/configuration/config_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3298,9 +3298,9 @@ This setting has the following sub-options:
The default is 'uid'.
* `attribute_requirements`: It is possible to configure Synapse to only allow logins if SAML attributes
match particular values. The requirements can be listed under
`attribute_requirements` as shown in the example. All of the listed attributes must
match for the login to be permitted. Values can be comma-seperated to allow
multiple values for one attribute.
`attribute_requirements` as shown in the example. All of the listed attributes must
match for the login to be permitted. Values can be specified in a `one_of` list to allow
multiple values for an attribute.
* `idp_entityid`: If the metadata XML contains multiple IdP entities then the `idp_entityid`
option must be set to the entity to redirect users to.
Most deployments only have a single IdP entity and so should omit this option.
Expand Down Expand Up @@ -3381,7 +3381,9 @@ saml2_config:
- attribute: userGroup
value: "staff"
- attribute: department
value: "sales,admins"
one_of:
- "sales"
- "admins"
idp_entityid: 'https://our_idp/entityid'
```
Expand Down
2 changes: 1 addition & 1 deletion synapse/config/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def _parse_oidc_config_dict(
)
# parse attribute_requirements from config (list of dicts) into a list of SsoAttributeRequirement
attribute_requirements = [
SsoAttributeRequirement(**x)
SsoAttributeRequirement.from_dict(x)
for x in oidc_config.get("attribute_requirements", [])
]

Expand Down
2 changes: 1 addition & 1 deletion synapse/config/saml2.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,4 +245,4 @@ def _parse_attribute_requirements_def(
attribute_requirements,
config_path=("saml2_config", "attribute_requirements"),
)
return [SsoAttributeRequirement(**x) for x in attribute_requirements]
return [SsoAttributeRequirement.from_dict(x) for x in attribute_requirements]
22 changes: 19 additions & 3 deletions synapse/config/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#
#
import logging
from typing import Any, Dict, Optional
from typing import Any, Dict, List, Optional, Self

import attr

Expand All @@ -45,13 +45,29 @@ class SsoAttributeRequirement:
attribute: str
# If a value is not given, than the attribute must simply exist.
value: Optional[str]
one_of: Optional[List[str]]

JSON_SCHEMA = {
"type": "object",
"properties": {"attribute": {"type": "string"}, "value": {"type": "string"}},
"required": ["attribute", "value"],
"properties": {
"attribute": {"type": "string"},
"value": {"type": "string"},
"one_of": {"type": "array", "items": {"type": "string"}},
},
"required": ["attribute"],
#"oneOf": [
# {"required": ["value"]},
# {"required": ["one_of"]},
#],
}

@staticmethod
def from_dict(attr_req: Dict[str, Any]) -> Self:
attribute = attr_req["attribute"]
value = attr_req.get("value")
one_of = attr_req.get("one_of")
return SsoAttributeRequirement(attribute, value, one_of)


class SSOConfig(Config):
"""SSO Configuration"""
Expand Down
11 changes: 7 additions & 4 deletions synapse/handlers/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -1276,13 +1276,16 @@ def _check_attribute_requirement(
return False

# If the requirement is None, the attribute existing is enough.
if req.value is None:
if req.value is None and req.one_of is None:
return True

values = attributes[req.attribute]
for req_value in req.value.split(","):
if req_value in values:
return True
if req.value in values:
return True
if req.one_of:
for value in req.one_of:
if value in values:
return True

logger.info(
"SSO attribute %s did not match required value '%s' (was '%s')",
Expand Down
2 changes: 1 addition & 1 deletion tests/handlers/test_oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1271,7 +1271,7 @@ def test_attribute_requirements_contains(self) -> None:
{
"oidc_config": {
**DEFAULT_CONFIG,
"attribute_requirements": [{"attribute": "test", "value": "foo,bar"}],
"attribute_requirements": [{"attribute": "test", "one_of": ["foo", "bar"]}],
}
}
)
Expand Down
2 changes: 1 addition & 1 deletion tests/handlers/test_saml.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ def test_attribute_requirements(self) -> None:
{
"saml2_config": {
"attribute_requirements": [
{"attribute": "userGroup", "value": "staff,admin"},
{"attribute": "userGroup", "one_of": ["staff", "admin"]},
],
},
}
Expand Down

0 comments on commit 297c68c

Please sign in to comment.