|
15 | 15 | # limitations under the License. |
16 | 16 |
|
17 | 17 | import logging |
| 18 | +from typing import Any, List |
18 | 19 |
|
| 20 | +import attr |
19 | 21 | import jinja2 |
20 | 22 | import pkg_resources |
21 | 23 |
|
22 | 24 | from synapse.python_dependencies import DependencyException, check_requirements |
23 | 25 | from synapse.util.module_loader import load_module, load_python_module |
24 | 26 |
|
25 | 27 | from ._base import Config, ConfigError |
| 28 | +from ._util import validate_config |
26 | 29 |
|
27 | 30 | logger = logging.getLogger(__name__) |
28 | 31 |
|
@@ -80,6 +83,11 @@ def read_config(self, config, **kwargs): |
80 | 83 |
|
81 | 84 | self.saml2_enabled = True |
82 | 85 |
|
| 86 | + attribute_requirements = saml2_config.get("attribute_requirements") or [] |
| 87 | + self.attribute_requirements = _parse_attribute_requirements_def( |
| 88 | + attribute_requirements |
| 89 | + ) |
| 90 | + |
83 | 91 | self.saml2_grandfathered_mxid_source_attribute = saml2_config.get( |
84 | 92 | "grandfathered_mxid_source_attribute", "uid" |
85 | 93 | ) |
@@ -341,6 +349,17 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs): |
341 | 349 | # |
342 | 350 | #grandfathered_mxid_source_attribute: upn |
343 | 351 |
|
| 352 | + # It is possible to configure Synapse to only allow logins if SAML attributes |
| 353 | + # match particular values. The requirements can be listed under |
| 354 | + # `attribute_requirements` as shown below. All of the listed attributes must |
| 355 | + # match for the login to be permitted. |
| 356 | + # |
| 357 | + #attribute_requirements: |
| 358 | + # - attribute: userGroup |
| 359 | + # value: "staff" |
| 360 | + # - attribute: department |
| 361 | + # value: "sales" |
| 362 | +
|
344 | 363 | # Directory in which Synapse will try to find the template files below. |
345 | 364 | # If not set, default templates from within the Synapse package will be used. |
346 | 365 | # |
@@ -368,3 +387,34 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs): |
368 | 387 | """ % { |
369 | 388 | "config_dir_path": config_dir_path |
370 | 389 | } |
| 390 | + |
| 391 | + |
| 392 | +@attr.s(frozen=True) |
| 393 | +class SamlAttributeRequirement: |
| 394 | + """Object describing a single requirement for SAML attributes.""" |
| 395 | + |
| 396 | + attribute = attr.ib(type=str) |
| 397 | + value = attr.ib(type=str) |
| 398 | + |
| 399 | + JSON_SCHEMA = { |
| 400 | + "type": "object", |
| 401 | + "properties": {"attribute": {"type": "string"}, "value": {"type": "string"}}, |
| 402 | + "required": ["attribute", "value"], |
| 403 | + } |
| 404 | + |
| 405 | + |
| 406 | +ATTRIBUTE_REQUIREMENTS_SCHEMA = { |
| 407 | + "type": "array", |
| 408 | + "items": SamlAttributeRequirement.JSON_SCHEMA, |
| 409 | +} |
| 410 | + |
| 411 | + |
| 412 | +def _parse_attribute_requirements_def( |
| 413 | + attribute_requirements: Any, |
| 414 | +) -> List[SamlAttributeRequirement]: |
| 415 | + validate_config( |
| 416 | + ATTRIBUTE_REQUIREMENTS_SCHEMA, |
| 417 | + attribute_requirements, |
| 418 | + config_path=["saml2_config", "attribute_requirements"], |
| 419 | + ) |
| 420 | + return [SamlAttributeRequirement(**x) for x in attribute_requirements] |
0 commit comments