1
1
# -*- coding: utf-8 -*-
2
2
# Copyright 2020 Quentin Gliech
3
+ # Copyright 2020 The Matrix.org Foundation C.I.C.
3
4
#
4
5
# Licensed under the Apache License, Version 2.0 (the "License");
5
6
# you may not use this file except in compliance with the License.
13
14
# See the License for the specific language governing permissions and
14
15
# limitations under the License.
15
16
17
+ from typing import Optional , Type
18
+
19
+ import attr
20
+
21
+ from synapse .config ._util import validate_config
16
22
from synapse .python_dependencies import DependencyException , check_requirements
23
+ from synapse .types import Collection , JsonDict
17
24
from synapse .util .module_loader import load_module
18
25
19
26
from ._base import Config , ConfigError
@@ -25,65 +32,32 @@ class OIDCConfig(Config):
25
32
section = "oidc"
26
33
27
34
def read_config (self , config , ** kwargs ):
28
- self .oidc_enabled = False
35
+ validate_config (MAIN_CONFIG_SCHEMA , config , ())
36
+
37
+ self .oidc_provider = None # type: Optional[OidcProviderConfig]
29
38
30
39
oidc_config = config .get ("oidc_config" )
40
+ if oidc_config and oidc_config .get ("enabled" , False ):
41
+ validate_config (OIDC_PROVIDER_CONFIG_SCHEMA , oidc_config , "oidc_config" )
42
+ self .oidc_provider = _parse_oidc_config_dict (oidc_config )
31
43
32
- if not oidc_config or not oidc_config . get ( "enabled" , False ) :
44
+ if not self . oidc_provider :
33
45
return
34
46
35
47
try :
36
48
check_requirements ("oidc" )
37
49
except DependencyException as e :
38
- raise ConfigError (e .message )
50
+ raise ConfigError (e .message ) from e
39
51
40
52
public_baseurl = self .public_baseurl
41
53
if public_baseurl is None :
42
54
raise ConfigError ("oidc_config requires a public_baseurl to be set" )
43
55
self .oidc_callback_url = public_baseurl + "_synapse/oidc/callback"
44
56
45
- self .oidc_enabled = True
46
- self .oidc_discover = oidc_config .get ("discover" , True )
47
- self .oidc_issuer = oidc_config ["issuer" ]
48
- self .oidc_client_id = oidc_config ["client_id" ]
49
- self .oidc_client_secret = oidc_config ["client_secret" ]
50
- self .oidc_client_auth_method = oidc_config .get (
51
- "client_auth_method" , "client_secret_basic"
52
- )
53
- self .oidc_scopes = oidc_config .get ("scopes" , ["openid" ])
54
- self .oidc_authorization_endpoint = oidc_config .get ("authorization_endpoint" )
55
- self .oidc_token_endpoint = oidc_config .get ("token_endpoint" )
56
- self .oidc_userinfo_endpoint = oidc_config .get ("userinfo_endpoint" )
57
- self .oidc_jwks_uri = oidc_config .get ("jwks_uri" )
58
- self .oidc_skip_verification = oidc_config .get ("skip_verification" , False )
59
- self .oidc_user_profile_method = oidc_config .get ("user_profile_method" , "auto" )
60
- self .oidc_allow_existing_users = oidc_config .get ("allow_existing_users" , False )
61
-
62
- ump_config = oidc_config .get ("user_mapping_provider" , {})
63
- ump_config .setdefault ("module" , DEFAULT_USER_MAPPING_PROVIDER )
64
- ump_config .setdefault ("config" , {})
65
-
66
- (
67
- self .oidc_user_mapping_provider_class ,
68
- self .oidc_user_mapping_provider_config ,
69
- ) = load_module (ump_config , ("oidc_config" , "user_mapping_provider" ))
70
-
71
- # Ensure loaded user mapping module has defined all necessary methods
72
- required_methods = [
73
- "get_remote_user_id" ,
74
- "map_user_attributes" ,
75
- ]
76
- missing_methods = [
77
- method
78
- for method in required_methods
79
- if not hasattr (self .oidc_user_mapping_provider_class , method )
80
- ]
81
- if missing_methods :
82
- raise ConfigError (
83
- "Class specified by oidc_config."
84
- "user_mapping_provider.module is missing required "
85
- "methods: %s" % (", " .join (missing_methods ),)
86
- )
57
+ @property
58
+ def oidc_enabled (self ) -> bool :
59
+ # OIDC is enabled if we have a provider
60
+ return bool (self .oidc_provider )
87
61
88
62
def generate_config_section (self , config_dir_path , server_name , ** kwargs ):
89
63
return """\
@@ -224,3 +198,154 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs):
224
198
""" .format (
225
199
mapping_provider = DEFAULT_USER_MAPPING_PROVIDER
226
200
)
201
+
202
+
203
+ # jsonschema definition of the configuration settings for an oidc identity provider
204
+ OIDC_PROVIDER_CONFIG_SCHEMA = {
205
+ "type" : "object" ,
206
+ "required" : ["issuer" , "client_id" , "client_secret" ],
207
+ "properties" : {
208
+ "discover" : {"type" : "boolean" },
209
+ "issuer" : {"type" : "string" },
210
+ "client_id" : {"type" : "string" },
211
+ "client_secret" : {"type" : "string" },
212
+ "client_auth_method" : {
213
+ "type" : "string" ,
214
+ # the following list is the same as the keys of
215
+ # authlib.oauth2.auth.ClientAuth.DEFAULT_AUTH_METHODS. We inline it
216
+ # to avoid importing authlib here.
217
+ "enum" : ["client_secret_basic" , "client_secret_post" , "none" ],
218
+ },
219
+ "scopes" : {"type" : "array" , "items" : {"type" : "string" }},
220
+ "authorization_endpoint" : {"type" : "string" },
221
+ "token_endpoint" : {"type" : "string" },
222
+ "userinfo_endpoint" : {"type" : "string" },
223
+ "jwks_uri" : {"type" : "string" },
224
+ "skip_verification" : {"type" : "boolean" },
225
+ "user_profile_method" : {
226
+ "type" : "string" ,
227
+ "enum" : ["auto" , "userinfo_endpoint" ],
228
+ },
229
+ "allow_existing_users" : {"type" : "boolean" },
230
+ "user_mapping_provider" : {"type" : ["object" , "null" ]},
231
+ },
232
+ }
233
+
234
+ # the `oidc_config` setting can either be None (as it is in the default
235
+ # config), or an object. If an object, it is ignored unless it has an "enabled: True"
236
+ # property.
237
+ #
238
+ # It's *possible* to represent this with jsonschema, but the resultant errors aren't
239
+ # particularly clear, so we just check for either an object or a null here, and do
240
+ # additional checks in the code.
241
+ OIDC_CONFIG_SCHEMA = {"oneOf" : [{"type" : "null" }, {"type" : "object" }]}
242
+
243
+ MAIN_CONFIG_SCHEMA = {
244
+ "type" : "object" ,
245
+ "properties" : {"oidc_config" : OIDC_CONFIG_SCHEMA },
246
+ }
247
+
248
+
249
+ def _parse_oidc_config_dict (oidc_config : JsonDict ) -> "OidcProviderConfig" :
250
+ """Take the configuration dict and parse it into an OidcProviderConfig
251
+
252
+ Raises:
253
+ ConfigError if the configuration is malformed.
254
+ """
255
+ ump_config = oidc_config .get ("user_mapping_provider" , {})
256
+ ump_config .setdefault ("module" , DEFAULT_USER_MAPPING_PROVIDER )
257
+ ump_config .setdefault ("config" , {})
258
+
259
+ (user_mapping_provider_class , user_mapping_provider_config ,) = load_module (
260
+ ump_config , ("oidc_config" , "user_mapping_provider" )
261
+ )
262
+
263
+ # Ensure loaded user mapping module has defined all necessary methods
264
+ required_methods = [
265
+ "get_remote_user_id" ,
266
+ "map_user_attributes" ,
267
+ ]
268
+ missing_methods = [
269
+ method
270
+ for method in required_methods
271
+ if not hasattr (user_mapping_provider_class , method )
272
+ ]
273
+ if missing_methods :
274
+ raise ConfigError (
275
+ "Class specified by oidc_config."
276
+ "user_mapping_provider.module is missing required "
277
+ "methods: %s" % (", " .join (missing_methods ),)
278
+ )
279
+
280
+ return OidcProviderConfig (
281
+ discover = oidc_config .get ("discover" , True ),
282
+ issuer = oidc_config ["issuer" ],
283
+ client_id = oidc_config ["client_id" ],
284
+ client_secret = oidc_config ["client_secret" ],
285
+ client_auth_method = oidc_config .get ("client_auth_method" , "client_secret_basic" ),
286
+ scopes = oidc_config .get ("scopes" , ["openid" ]),
287
+ authorization_endpoint = oidc_config .get ("authorization_endpoint" ),
288
+ token_endpoint = oidc_config .get ("token_endpoint" ),
289
+ userinfo_endpoint = oidc_config .get ("userinfo_endpoint" ),
290
+ jwks_uri = oidc_config .get ("jwks_uri" ),
291
+ skip_verification = oidc_config .get ("skip_verification" , False ),
292
+ user_profile_method = oidc_config .get ("user_profile_method" , "auto" ),
293
+ allow_existing_users = oidc_config .get ("allow_existing_users" , False ),
294
+ user_mapping_provider_class = user_mapping_provider_class ,
295
+ user_mapping_provider_config = user_mapping_provider_config ,
296
+ )
297
+
298
+
299
+ @attr .s
300
+ class OidcProviderConfig :
301
+ # whether the OIDC discovery mechanism is used to discover endpoints
302
+ discover = attr .ib (type = bool )
303
+
304
+ # the OIDC issuer. Used to validate tokens and (if discovery is enabled) to
305
+ # discover the provider's endpoints.
306
+ issuer = attr .ib (type = str )
307
+
308
+ # oauth2 client id to use
309
+ client_id = attr .ib (type = str )
310
+
311
+ # oauth2 client secret to use
312
+ client_secret = attr .ib (type = str )
313
+
314
+ # auth method to use when exchanging the token.
315
+ # Valid values are 'client_secret_basic', 'client_secret_post' and
316
+ # 'none'.
317
+ client_auth_method = attr .ib (type = str )
318
+
319
+ # list of scopes to request
320
+ scopes = attr .ib (type = Collection [str ])
321
+
322
+ # the oauth2 authorization endpoint. Required if discovery is disabled.
323
+ authorization_endpoint = attr .ib (type = Optional [str ])
324
+
325
+ # the oauth2 token endpoint. Required if discovery is disabled.
326
+ token_endpoint = attr .ib (type = Optional [str ])
327
+
328
+ # the OIDC userinfo endpoint. Required if discovery is disabled and the
329
+ # "openid" scope is not requested.
330
+ userinfo_endpoint = attr .ib (type = Optional [str ])
331
+
332
+ # URI where to fetch the JWKS. Required if discovery is disabled and the
333
+ # "openid" scope is used.
334
+ jwks_uri = attr .ib (type = Optional [str ])
335
+
336
+ # Whether to skip metadata verification
337
+ skip_verification = attr .ib (type = bool )
338
+
339
+ # Whether to fetch the user profile from the userinfo endpoint. Valid
340
+ # values are: "auto" or "userinfo_endpoint".
341
+ user_profile_method = attr .ib (type = str )
342
+
343
+ # whether to allow a user logging in via OIDC to match a pre-existing account
344
+ # instead of failing
345
+ allow_existing_users = attr .ib (type = bool )
346
+
347
+ # the class of the user mapping provider
348
+ user_mapping_provider_class = attr .ib (type = Type )
349
+
350
+ # the config of the user mapping provider
351
+ user_mapping_provider_config = attr .ib ()
0 commit comments