summary refs log tree commit diff
path: root/synapse/config/oidc_config.py
blob: e0939bce84a1dab62a5acfe587f6e93abcbb345b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# -*- coding: utf-8 -*-
# Copyright 2020 Quentin Gliech
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from synapse.python_dependencies import DependencyException, check_requirements
from synapse.util.module_loader import load_module

from ._base import Config, ConfigError

DEFAULT_USER_MAPPING_PROVIDER = "synapse.handlers.oidc_handler.JinjaOidcMappingProvider"


class OIDCConfig(Config):
    section = "oidc"

    def read_config(self, config, **kwargs):
        self.oidc_enabled = False

        oidc_config = config.get("oidc_config")

        if not oidc_config or not oidc_config.get("enabled", False):
            return

        try:
            check_requirements("oidc")
        except DependencyException as e:
            raise ConfigError(e.message)

        public_baseurl = self.public_baseurl
        if public_baseurl is None:
            raise ConfigError("oidc_config requires a public_baseurl to be set")
        self.oidc_callback_url = public_baseurl + "_synapse/oidc/callback"

        self.oidc_enabled = True
        self.oidc_discover = oidc_config.get("discover", True)
        self.oidc_issuer = oidc_config["issuer"]
        self.oidc_client_id = oidc_config["client_id"]
        self.oidc_client_secret = oidc_config["client_secret"]
        self.oidc_client_auth_method = oidc_config.get(
            "client_auth_method", "client_secret_basic"
        )
        self.oidc_scopes = oidc_config.get("scopes", ["openid"])
        self.oidc_authorization_endpoint = oidc_config.get("authorization_endpoint")
        self.oidc_token_endpoint = oidc_config.get("token_endpoint")
        self.oidc_userinfo_endpoint = oidc_config.get("userinfo_endpoint")
        self.oidc_jwks_uri = oidc_config.get("jwks_uri")
        self.oidc_skip_verification = oidc_config.get("skip_verification", False)

        ump_config = oidc_config.get("user_mapping_provider", {})
        ump_config.setdefault("module", DEFAULT_USER_MAPPING_PROVIDER)
        ump_config.setdefault("config", {})

        (
            self.oidc_user_mapping_provider_class,
            self.oidc_user_mapping_provider_config,
        ) = load_module(ump_config)

        # Ensure loaded user mapping module has defined all necessary methods
        required_methods = [
            "get_remote_user_id",
            "map_user_attributes",
        ]
        missing_methods = [
            method
            for method in required_methods
            if not hasattr(self.oidc_user_mapping_provider_class, method)
        ]
        if missing_methods:
            raise ConfigError(
                "Class specified by oidc_config."
                "user_mapping_provider.module is missing required "
                "methods: %s" % (", ".join(missing_methods),)
            )

    def generate_config_section(self, config_dir_path, server_name, **kwargs):
        return """\
        # OpenID Connect integration. The following settings can be used to make Synapse
        # use an OpenID Connect Provider for authentication, instead of its internal
        # password database.
        #
        # See https://github.com/matrix-org/synapse/blob/master/docs/openid.md.
        #
        oidc_config:
          # Uncomment the following to enable authorization against an OpenID Connect
          # server. Defaults to false.
          #
          #enabled: true

          # Uncomment the following to disable use of the OIDC discovery mechanism to
          # discover endpoints. Defaults to true.
          #
          #discover: false

          # the OIDC issuer. Used to validate tokens and (if discovery is enabled) to
          # discover the provider's endpoints.
          #
          # Required if 'enabled' is true.
          #
          #issuer: "https://accounts.example.com/"

          # oauth2 client id to use.
          #
          # Required if 'enabled' is true.
          #
          #client_id: "provided-by-your-issuer"

          # oauth2 client secret to use.
          #
          # Required if 'enabled' is true.
          #
          #client_secret: "provided-by-your-issuer"

          # auth method to use when exchanging the token.
          # Valid values are 'client_secret_basic' (default), 'client_secret_post' and
          # 'none'.
          #
          #client_auth_method: client_secret_post

          # list of scopes to request. This should normally include the "openid" scope.
          # Defaults to ["openid"].
          #
          #scopes: ["openid", "profile"]

          # the oauth2 authorization endpoint. Required if provider discovery is disabled.
          #
          #authorization_endpoint: "https://accounts.example.com/oauth2/auth"

          # the oauth2 token endpoint. Required if provider discovery is disabled.
          #
          #token_endpoint: "https://accounts.example.com/oauth2/token"

          # the OIDC userinfo endpoint. Required if discovery is disabled and the
          # "openid" scope is not requested.
          #
          #userinfo_endpoint: "https://accounts.example.com/userinfo"

          # URI where to fetch the JWKS. Required if discovery is disabled and the
          # "openid" scope is used.
          #
          #jwks_uri: "https://accounts.example.com/.well-known/jwks.json"

          # Uncomment to skip metadata verification. Defaults to false.
          #
          # Use this if you are connecting to a provider that is not OpenID Connect
          # compliant.
          # Avoid this in production.
          #
          #skip_verification: true

          # An external module can be provided here as a custom solution to mapping
          # attributes returned from a OIDC provider onto a matrix user.
          #
          user_mapping_provider:
            # The custom module's class. Uncomment to use a custom module.
            # Default is {mapping_provider!r}.
            #
            # See https://github.com/matrix-org/synapse/blob/master/docs/sso_mapping_providers.md#openid-mapping-providers
            # for information on implementing a custom mapping provider.
            #
            #module: mapping_provider.OidcMappingProvider

            # Custom configuration values for the module. This section will be passed as
            # a Python dictionary to the user mapping provider module's `parse_config`
            # method.
            #
            # The examples below are intended for the default provider: they should be
            # changed if using a custom provider.
            #
            config:
              # name of the claim containing a unique identifier for the user.
              # Defaults to `sub`, which OpenID Connect compliant providers should provide.
              #
              #subject_claim: "sub"

              # Jinja2 template for the localpart of the MXID.
              #
              # When rendering, this template is given the following variables:
              #   * user: The claims returned by the UserInfo Endpoint and/or in the ID
              #     Token
              #
              # This must be configured if using the default mapping provider.
              #
              localpart_template: "{{{{ user.preferred_username }}}}"

              # Jinja2 template for the display name to set on first login.
              #
              # If unset, no displayname will be set.
              #
              #display_name_template: "{{{{ user.given_name }}}} {{{{ user.last_name }}}}"
        """.format(
            mapping_provider=DEFAULT_USER_MAPPING_PROVIDER
        )