From dd5e5dc1d6c88a3532d25f18cfc312d8bc813473 Mon Sep 17 00:00:00 2001 From: Hubbe Date: Tue, 16 Mar 2021 17:46:07 +0200 Subject: Add SSO attribute requirements for OIDC providers (#9609) Allows limiting who can login using OIDC via the claims made from the IdP. --- synapse/config/oidc_config.py | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) (limited to 'synapse/config/oidc_config.py') diff --git a/synapse/config/oidc_config.py b/synapse/config/oidc_config.py index 2bfb537c15..eab042a085 100644 --- a/synapse/config/oidc_config.py +++ b/synapse/config/oidc_config.py @@ -15,11 +15,12 @@ # limitations under the License. from collections import Counter -from typing import Iterable, Mapping, Optional, Tuple, Type +from typing import Iterable, List, Mapping, Optional, Tuple, Type import attr from synapse.config._util import validate_config +from synapse.config.sso import SsoAttributeRequirement from synapse.python_dependencies import DependencyException, check_requirements from synapse.types import Collection, JsonDict from synapse.util.module_loader import load_module @@ -191,6 +192,24 @@ class OIDCConfig(Config): # which is set to the claims returned by the UserInfo Endpoint and/or # in the ID Token. # + # It is possible to configure Synapse to only allow logins if certain attributes + # match particular values in the OIDC userinfo. The requirements can be listed under + # `attribute_requirements` as shown below. All of the listed attributes must + # match for the login to be permitted. Additional attributes can be added to + # userinfo by expanding the `scopes` section of the OIDC config to retrieve + # additional information from the OIDC provider. + # + # If the OIDC claim is a list, then the attribute must match any value in the list. + # Otherwise, it must exactly match the value of the claim. Using the example + # below, the `family_name` claim MUST be "Stephensson", but the `groups` + # claim MUST contain "admin". + # + # attribute_requirements: + # - attribute: family_name + # value: "Stephensson" + # - attribute: groups + # value: "admin" + # # See https://github.com/matrix-org/synapse/blob/master/docs/openid.md # for information on how to configure these options. # @@ -223,6 +242,9 @@ class OIDCConfig(Config): # localpart_template: "{{{{ user.login }}}}" # display_name_template: "{{{{ user.name }}}}" # email_template: "{{{{ user.email }}}}" + # attribute_requirements: + # - attribute: userGroup + # value: "synapseUsers" # For use with Keycloak # @@ -232,6 +254,9 @@ class OIDCConfig(Config): # client_id: "synapse" # client_secret: "copy secret generated in Keycloak UI" # scopes: ["openid", "profile"] + # attribute_requirements: + # - attribute: groups + # value: "admin" # For use with Github # @@ -329,6 +354,10 @@ OIDC_PROVIDER_CONFIG_SCHEMA = { }, "allow_existing_users": {"type": "boolean"}, "user_mapping_provider": {"type": ["object", "null"]}, + "attribute_requirements": { + "type": "array", + "items": SsoAttributeRequirement.JSON_SCHEMA, + }, }, } @@ -465,6 +494,11 @@ def _parse_oidc_config_dict( jwt_header=client_secret_jwt_key_config["jwt_header"], jwt_payload=client_secret_jwt_key_config.get("jwt_payload", {}), ) + # parse attribute_requirements from config (list of dicts) into a list of SsoAttributeRequirement + attribute_requirements = [ + SsoAttributeRequirement(**x) + for x in oidc_config.get("attribute_requirements", []) + ] return OidcProviderConfig( idp_id=idp_id, @@ -488,6 +522,7 @@ def _parse_oidc_config_dict( allow_existing_users=oidc_config.get("allow_existing_users", False), user_mapping_provider_class=user_mapping_provider_class, user_mapping_provider_config=user_mapping_provider_config, + attribute_requirements=attribute_requirements, ) @@ -577,3 +612,6 @@ class OidcProviderConfig: # the config of the user mapping provider user_mapping_provider_config = attr.ib() + + # required attributes to require in userinfo to allow login/registration + attribute_requirements = attr.ib(type=List[SsoAttributeRequirement]) -- cgit 1.5.1 From 27d2820c33d94cd99aea128b6ade76a7de838c3d Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Tue, 16 Mar 2021 19:19:27 +0100 Subject: Enable flake8-bugbear, but disable most checks. (#9499) * Adds B00 to ignored checks. * Fixes remaining issues. --- changelog.d/9499.misc | 1 + setup.cfg | 3 ++- setup.py | 1 + synapse/app/__init__.py | 4 +++- synapse/config/key.py | 6 +++++- synapse/config/metrics.py | 4 +++- synapse/config/oidc_config.py | 4 +++- synapse/config/repository.py | 4 +++- synapse/config/saml2_config.py | 4 +++- synapse/config/tracer.py | 4 +++- synapse/crypto/context_factory.py | 2 +- tests/unittest.py | 2 +- 12 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 changelog.d/9499.misc (limited to 'synapse/config/oidc_config.py') diff --git a/changelog.d/9499.misc b/changelog.d/9499.misc new file mode 100644 index 0000000000..1513017a10 --- /dev/null +++ b/changelog.d/9499.misc @@ -0,0 +1 @@ +Introduce bugbear to the test suite and fix some of it's lint violations. \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 5e301c2cd7..920868df20 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,8 @@ ignore = # E203: whitespace before ':' (which is contrary to pep8?) # E731: do not assign a lambda expression, use a def # E501: Line too long (black enforces this for us) -ignore=W503,W504,E203,E731,E501 +# B00: Subsection of the bugbear suite (TODO: add in remaining fixes) +ignore=W503,W504,E203,E731,E501,B00 [isort] line_length = 88 diff --git a/setup.py b/setup.py index bbd9e7862a..b834e4e55b 100755 --- a/setup.py +++ b/setup.py @@ -99,6 +99,7 @@ CONDITIONAL_REQUIREMENTS["lint"] = [ "isort==5.7.0", "black==20.8b1", "flake8-comprehensions", + "flake8-bugbear", "flake8", ] diff --git a/synapse/app/__init__.py b/synapse/app/__init__.py index 4a9b0129c3..d1a2cd5e19 100644 --- a/synapse/app/__init__.py +++ b/synapse/app/__init__.py @@ -22,7 +22,9 @@ logger = logging.getLogger(__name__) try: python_dependencies.check_requirements() except python_dependencies.DependencyException as e: - sys.stderr.writelines(e.message) + sys.stderr.writelines( + e.message # noqa: B306, DependencyException.message is a property + ) sys.exit(1) diff --git a/synapse/config/key.py b/synapse/config/key.py index de964dff13..350ff1d665 100644 --- a/synapse/config/key.py +++ b/synapse/config/key.py @@ -404,7 +404,11 @@ def _parse_key_servers(key_servers, federation_verify_certificates): try: jsonschema.validate(key_servers, TRUSTED_KEY_SERVERS_SCHEMA) except jsonschema.ValidationError as e: - raise ConfigError("Unable to parse 'trusted_key_servers': " + e.message) + raise ConfigError( + "Unable to parse 'trusted_key_servers': {}".format( + e.message # noqa: B306, jsonschema.ValidationError.message is a valid attribute + ) + ) for server in key_servers: server_name = server["server_name"] diff --git a/synapse/config/metrics.py b/synapse/config/metrics.py index dfd27e1523..2b289f4208 100644 --- a/synapse/config/metrics.py +++ b/synapse/config/metrics.py @@ -56,7 +56,9 @@ class MetricsConfig(Config): try: check_requirements("sentry") except DependencyException as e: - raise ConfigError(e.message) + raise ConfigError( + e.message # noqa: B306, DependencyException.message is a property + ) self.sentry_dsn = config["sentry"].get("dsn") if not self.sentry_dsn: diff --git a/synapse/config/oidc_config.py b/synapse/config/oidc_config.py index eab042a085..747ab9a7fe 100644 --- a/synapse/config/oidc_config.py +++ b/synapse/config/oidc_config.py @@ -42,7 +42,9 @@ class OIDCConfig(Config): try: check_requirements("oidc") except DependencyException as e: - raise ConfigError(e.message) from e + raise ConfigError( + e.message # noqa: B306, DependencyException.message is a property + ) from e # check we don't have any duplicate idp_ids now. (The SSO handler will also # check for duplicates when the REST listeners get registered, but that happens diff --git a/synapse/config/repository.py b/synapse/config/repository.py index 69d9de5a43..061c4ec83f 100644 --- a/synapse/config/repository.py +++ b/synapse/config/repository.py @@ -176,7 +176,9 @@ class ContentRepositoryConfig(Config): check_requirements("url_preview") except DependencyException as e: - raise ConfigError(e.message) + raise ConfigError( + e.message # noqa: B306, DependencyException.message is a property + ) if "url_preview_ip_range_blacklist" not in config: raise ConfigError( diff --git a/synapse/config/saml2_config.py b/synapse/config/saml2_config.py index 4b494f217f..6db9cb5ced 100644 --- a/synapse/config/saml2_config.py +++ b/synapse/config/saml2_config.py @@ -76,7 +76,9 @@ class SAML2Config(Config): try: check_requirements("saml2") except DependencyException as e: - raise ConfigError(e.message) + raise ConfigError( + e.message # noqa: B306, DependencyException.message is a property + ) self.saml2_enabled = True diff --git a/synapse/config/tracer.py b/synapse/config/tracer.py index 0c1a854f09..727a1e7008 100644 --- a/synapse/config/tracer.py +++ b/synapse/config/tracer.py @@ -39,7 +39,9 @@ class TracerConfig(Config): try: check_requirements("opentracing") except DependencyException as e: - raise ConfigError(e.message) + raise ConfigError( + e.message # noqa: B306, DependencyException.message is a property + ) # The tracer is enabled so sanitize the config diff --git a/synapse/crypto/context_factory.py b/synapse/crypto/context_factory.py index 14b21796d9..4ca13011e5 100644 --- a/synapse/crypto/context_factory.py +++ b/synapse/crypto/context_factory.py @@ -219,7 +219,7 @@ class SSLClientConnectionCreator: # ... and we also gut-wrench a '_synapse_tls_verifier' attribute into the # tls_protocol so that the SSL context's info callback has something to # call to do the cert verification. - setattr(tls_protocol, "_synapse_tls_verifier", self._verifier) + tls_protocol._synapse_tls_verifier = self._verifier return connection diff --git a/tests/unittest.py b/tests/unittest.py index ca7031c724..224f037ce1 100644 --- a/tests/unittest.py +++ b/tests/unittest.py @@ -140,7 +140,7 @@ class TestCase(unittest.TestCase): try: self.assertEquals(attrs[key], getattr(obj, key)) except AssertionError as e: - raise (type(e))(e.message + " for '.%s'" % key) + raise (type(e))("Assert error for '.{}':".format(key)) from e def assert_dict(self, required, actual): """Does a partial assert of a dict. -- cgit 1.5.1 From 4bbd53545041c9295fbae5dd0cefdd66d55d0b53 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Mon, 29 Mar 2021 15:40:11 +0100 Subject: Update the OIDC sample config (#9695) I've reiterated the advice about using `oidc` to migrate, since I've seen a few people caught by this. I've also removed a couple of the examples as they are duplicating the OIDC documentation, and I think they might be leading people astray. --- changelog.d/9695.doc | 1 + docs/sample_config.yaml | 34 +++------------------------------- synapse/config/oidc_config.py | 34 +++------------------------------- 3 files changed, 7 insertions(+), 62 deletions(-) create mode 100644 changelog.d/9695.doc (limited to 'synapse/config/oidc_config.py') diff --git a/changelog.d/9695.doc b/changelog.d/9695.doc new file mode 100644 index 0000000000..cf82e68a85 --- /dev/null +++ b/changelog.d/9695.doc @@ -0,0 +1 @@ +Update the sample configuration for OIDC authentication. diff --git a/docs/sample_config.yaml b/docs/sample_config.yaml index 07a928224d..17cda71adc 100644 --- a/docs/sample_config.yaml +++ b/docs/sample_config.yaml @@ -1758,6 +1758,9 @@ saml2_config: # Note that, if this is changed, users authenticating via that provider # will no longer be recognised as the same user! # +# (Use "oidc" here if you are migrating from an old "oidc_config" +# configuration.) +# # idp_name: A user-facing name for this identity provider, which is used to # offer the user a choice of login mechanisms. # @@ -1927,37 +1930,6 @@ oidc_providers: # - attribute: userGroup # value: "synapseUsers" - # For use with Keycloak - # - #- idp_id: keycloak - # idp_name: Keycloak - # issuer: "https://127.0.0.1:8443/auth/realms/my_realm_name" - # client_id: "synapse" - # client_secret: "copy secret generated in Keycloak UI" - # scopes: ["openid", "profile"] - # attribute_requirements: - # - attribute: groups - # value: "admin" - - # For use with Github - # - #- idp_id: github - # idp_name: Github - # idp_brand: github - # discover: false - # issuer: "https://github.com/" - # client_id: "your-client-id" # TO BE FILLED - # client_secret: "your-client-secret" # TO BE FILLED - # authorization_endpoint: "https://github.com/login/oauth/authorize" - # token_endpoint: "https://github.com/login/oauth/access_token" - # userinfo_endpoint: "https://api.github.com/user" - # scopes: ["read:user"] - # user_mapping_provider: - # config: - # subject_claim: "id" - # localpart_template: "{{ user.login }}" - # display_name_template: "{{ user.name }}" - # Enable Central Authentication Service (CAS) for registration and login. # diff --git a/synapse/config/oidc_config.py b/synapse/config/oidc_config.py index 747ab9a7fe..05733ec41d 100644 --- a/synapse/config/oidc_config.py +++ b/synapse/config/oidc_config.py @@ -79,6 +79,9 @@ class OIDCConfig(Config): # Note that, if this is changed, users authenticating via that provider # will no longer be recognised as the same user! # + # (Use "oidc" here if you are migrating from an old "oidc_config" + # configuration.) + # # idp_name: A user-facing name for this identity provider, which is used to # offer the user a choice of login mechanisms. # @@ -247,37 +250,6 @@ class OIDCConfig(Config): # attribute_requirements: # - attribute: userGroup # value: "synapseUsers" - - # For use with Keycloak - # - #- idp_id: keycloak - # idp_name: Keycloak - # issuer: "https://127.0.0.1:8443/auth/realms/my_realm_name" - # client_id: "synapse" - # client_secret: "copy secret generated in Keycloak UI" - # scopes: ["openid", "profile"] - # attribute_requirements: - # - attribute: groups - # value: "admin" - - # For use with Github - # - #- idp_id: github - # idp_name: Github - # idp_brand: github - # discover: false - # issuer: "https://github.com/" - # client_id: "your-client-id" # TO BE FILLED - # client_secret: "your-client-secret" # TO BE FILLED - # authorization_endpoint: "https://github.com/login/oauth/authorize" - # token_endpoint: "https://github.com/login/oauth/access_token" - # userinfo_endpoint: "https://api.github.com/user" - # scopes: ["read:user"] - # user_mapping_provider: - # config: - # subject_claim: "id" - # localpart_template: "{{{{ user.login }}}}" - # display_name_template: "{{{{ user.name }}}}" """.format( mapping_provider=DEFAULT_USER_MAPPING_PROVIDER ) -- cgit 1.5.1