oidc_config.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2020 Quentin Gliech
  3. # Copyright 2020-2021 The Matrix.org Foundation C.I.C.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. from collections import Counter
  17. from typing import Iterable, Mapping, Optional, Tuple, Type
  18. import attr
  19. from synapse.config._util import validate_config
  20. from synapse.python_dependencies import DependencyException, check_requirements
  21. from synapse.types import Collection, JsonDict
  22. from synapse.util.module_loader import load_module
  23. from synapse.util.stringutils import parse_and_validate_mxc_uri
  24. from ._base import Config, ConfigError, read_file
  25. DEFAULT_USER_MAPPING_PROVIDER = "synapse.handlers.oidc_handler.JinjaOidcMappingProvider"
  26. class OIDCConfig(Config):
  27. section = "oidc"
  28. def read_config(self, config, **kwargs):
  29. self.oidc_providers = tuple(_parse_oidc_provider_configs(config))
  30. if not self.oidc_providers:
  31. return
  32. try:
  33. check_requirements("oidc")
  34. except DependencyException as e:
  35. raise ConfigError(e.message) from e
  36. # check we don't have any duplicate idp_ids now. (The SSO handler will also
  37. # check for duplicates when the REST listeners get registered, but that happens
  38. # after synapse has forked so doesn't give nice errors.)
  39. c = Counter([i.idp_id for i in self.oidc_providers])
  40. for idp_id, count in c.items():
  41. if count > 1:
  42. raise ConfigError(
  43. "Multiple OIDC providers have the idp_id %r." % idp_id
  44. )
  45. public_baseurl = self.public_baseurl
  46. if public_baseurl is None:
  47. raise ConfigError("oidc_config requires a public_baseurl to be set")
  48. self.oidc_callback_url = public_baseurl + "_synapse/client/oidc/callback"
  49. @property
  50. def oidc_enabled(self) -> bool:
  51. # OIDC is enabled if we have a provider
  52. return bool(self.oidc_providers)
  53. def generate_config_section(self, config_dir_path, server_name, **kwargs):
  54. return """\
  55. # List of OpenID Connect (OIDC) / OAuth 2.0 identity providers, for registration
  56. # and login.
  57. #
  58. # Options for each entry include:
  59. #
  60. # idp_id: a unique identifier for this identity provider. Used internally
  61. # by Synapse; should be a single word such as 'github'.
  62. #
  63. # Note that, if this is changed, users authenticating via that provider
  64. # will no longer be recognised as the same user!
  65. #
  66. # idp_name: A user-facing name for this identity provider, which is used to
  67. # offer the user a choice of login mechanisms.
  68. #
  69. # idp_icon: An optional icon for this identity provider, which is presented
  70. # by clients and Synapse's own IdP picker page. If given, must be an
  71. # MXC URI of the format mxc://<server-name>/<media-id>. (An easy way to
  72. # obtain such an MXC URI is to upload an image to an (unencrypted) room
  73. # and then copy the "url" from the source of the event.)
  74. #
  75. # idp_brand: An optional brand for this identity provider, allowing clients
  76. # to style the login flow according to the identity provider in question.
  77. # See the spec for possible options here.
  78. #
  79. # discover: set to 'false' to disable the use of the OIDC discovery mechanism
  80. # to discover endpoints. Defaults to true.
  81. #
  82. # issuer: Required. The OIDC issuer. Used to validate tokens and (if discovery
  83. # is enabled) to discover the provider's endpoints.
  84. #
  85. # client_id: Required. oauth2 client id to use.
  86. #
  87. # client_secret: oauth2 client secret to use. May be omitted if
  88. # client_secret_jwt_key is given, or if client_auth_method is 'none'.
  89. #
  90. # client_secret_jwt_key: Alternative to client_secret: details of a key used
  91. # to create a JSON Web Token to be used as an OAuth2 client secret. If
  92. # given, must be a dictionary with the following properties:
  93. #
  94. # key: a pem-encoded signing key. Must be a suitable key for the
  95. # algorithm specified. Required unless 'key_file' is given.
  96. #
  97. # key_file: the path to file containing a pem-encoded signing key file.
  98. # Required unless 'key' is given.
  99. #
  100. # jwt_header: a dictionary giving properties to include in the JWT
  101. # header. Must include the key 'alg', giving the algorithm used to
  102. # sign the JWT, such as "ES256", using the JWA identifiers in
  103. # RFC7518.
  104. #
  105. # jwt_payload: an optional dictionary giving properties to include in
  106. # the JWT payload. Normally this should include an 'iss' key.
  107. #
  108. # client_auth_method: auth method to use when exchanging the token. Valid
  109. # values are 'client_secret_basic' (default), 'client_secret_post' and
  110. # 'none'.
  111. #
  112. # scopes: list of scopes to request. This should normally include the "openid"
  113. # scope. Defaults to ["openid"].
  114. #
  115. # authorization_endpoint: the oauth2 authorization endpoint. Required if
  116. # provider discovery is disabled.
  117. #
  118. # token_endpoint: the oauth2 token endpoint. Required if provider discovery is
  119. # disabled.
  120. #
  121. # userinfo_endpoint: the OIDC userinfo endpoint. Required if discovery is
  122. # disabled and the 'openid' scope is not requested.
  123. #
  124. # jwks_uri: URI where to fetch the JWKS. Required if discovery is disabled and
  125. # the 'openid' scope is used.
  126. #
  127. # skip_verification: set to 'true' to skip metadata verification. Use this if
  128. # you are connecting to a provider that is not OpenID Connect compliant.
  129. # Defaults to false. Avoid this in production.
  130. #
  131. # user_profile_method: Whether to fetch the user profile from the userinfo
  132. # endpoint. Valid values are: 'auto' or 'userinfo_endpoint'.
  133. #
  134. # Defaults to 'auto', which fetches the userinfo endpoint if 'openid' is
  135. # included in 'scopes'. Set to 'userinfo_endpoint' to always fetch the
  136. # userinfo endpoint.
  137. #
  138. # allow_existing_users: set to 'true' to allow a user logging in via OIDC to
  139. # match a pre-existing account instead of failing. This could be used if
  140. # switching from password logins to OIDC. Defaults to false.
  141. #
  142. # user_mapping_provider: Configuration for how attributes returned from a OIDC
  143. # provider are mapped onto a matrix user. This setting has the following
  144. # sub-properties:
  145. #
  146. # module: The class name of a custom mapping module. Default is
  147. # {mapping_provider!r}.
  148. # See https://github.com/matrix-org/synapse/blob/master/docs/sso_mapping_providers.md#openid-mapping-providers
  149. # for information on implementing a custom mapping provider.
  150. #
  151. # config: Configuration for the mapping provider module. This section will
  152. # be passed as a Python dictionary to the user mapping provider
  153. # module's `parse_config` method.
  154. #
  155. # For the default provider, the following settings are available:
  156. #
  157. # subject_claim: name of the claim containing a unique identifier
  158. # for the user. Defaults to 'sub', which OpenID Connect
  159. # compliant providers should provide.
  160. #
  161. # localpart_template: Jinja2 template for the localpart of the MXID.
  162. # If this is not set, the user will be prompted to choose their
  163. # own username (see 'sso_auth_account_details.html' in the 'sso'
  164. # section of this file).
  165. #
  166. # display_name_template: Jinja2 template for the display name to set
  167. # on first login. If unset, no displayname will be set.
  168. #
  169. # email_template: Jinja2 template for the email address of the user.
  170. # If unset, no email address will be added to the account.
  171. #
  172. # extra_attributes: a map of Jinja2 templates for extra attributes
  173. # to send back to the client during login.
  174. # Note that these are non-standard and clients will ignore them
  175. # without modifications.
  176. #
  177. # When rendering, the Jinja2 templates are given a 'user' variable,
  178. # which is set to the claims returned by the UserInfo Endpoint and/or
  179. # in the ID Token.
  180. #
  181. # See https://github.com/matrix-org/synapse/blob/master/docs/openid.md
  182. # for information on how to configure these options.
  183. #
  184. # For backwards compatibility, it is also possible to configure a single OIDC
  185. # provider via an 'oidc_config' setting. This is now deprecated and admins are
  186. # advised to migrate to the 'oidc_providers' format. (When doing that migration,
  187. # use 'oidc' for the idp_id to ensure that existing users continue to be
  188. # recognised.)
  189. #
  190. oidc_providers:
  191. # Generic example
  192. #
  193. #- idp_id: my_idp
  194. # idp_name: "My OpenID provider"
  195. # idp_icon: "mxc://example.com/mediaid"
  196. # discover: false
  197. # issuer: "https://accounts.example.com/"
  198. # client_id: "provided-by-your-issuer"
  199. # client_secret: "provided-by-your-issuer"
  200. # client_auth_method: client_secret_post
  201. # scopes: ["openid", "profile"]
  202. # authorization_endpoint: "https://accounts.example.com/oauth2/auth"
  203. # token_endpoint: "https://accounts.example.com/oauth2/token"
  204. # userinfo_endpoint: "https://accounts.example.com/userinfo"
  205. # jwks_uri: "https://accounts.example.com/.well-known/jwks.json"
  206. # skip_verification: true
  207. # user_mapping_provider:
  208. # config:
  209. # subject_claim: "id"
  210. # localpart_template: "{{{{ user.login }}}}"
  211. # display_name_template: "{{{{ user.name }}}}"
  212. # email_template: "{{{{ user.email }}}}"
  213. # For use with Keycloak
  214. #
  215. #- idp_id: keycloak
  216. # idp_name: Keycloak
  217. # issuer: "https://127.0.0.1:8443/auth/realms/my_realm_name"
  218. # client_id: "synapse"
  219. # client_secret: "copy secret generated in Keycloak UI"
  220. # scopes: ["openid", "profile"]
  221. # For use with Github
  222. #
  223. #- idp_id: github
  224. # idp_name: Github
  225. # idp_brand: org.matrix.github
  226. # discover: false
  227. # issuer: "https://github.com/"
  228. # client_id: "your-client-id" # TO BE FILLED
  229. # client_secret: "your-client-secret" # TO BE FILLED
  230. # authorization_endpoint: "https://github.com/login/oauth/authorize"
  231. # token_endpoint: "https://github.com/login/oauth/access_token"
  232. # userinfo_endpoint: "https://api.github.com/user"
  233. # scopes: ["read:user"]
  234. # user_mapping_provider:
  235. # config:
  236. # subject_claim: "id"
  237. # localpart_template: "{{{{ user.login }}}}"
  238. # display_name_template: "{{{{ user.name }}}}"
  239. """.format(
  240. mapping_provider=DEFAULT_USER_MAPPING_PROVIDER
  241. )
  242. # jsonschema definition of the configuration settings for an oidc identity provider
  243. OIDC_PROVIDER_CONFIG_SCHEMA = {
  244. "type": "object",
  245. "required": ["issuer", "client_id"],
  246. "properties": {
  247. "idp_id": {
  248. "type": "string",
  249. "minLength": 1,
  250. # MSC2858 allows a maxlen of 255, but we prefix with "oidc-"
  251. "maxLength": 250,
  252. "pattern": "^[A-Za-z0-9._~-]+$",
  253. },
  254. "idp_name": {"type": "string"},
  255. "idp_icon": {"type": "string"},
  256. "idp_brand": {
  257. "type": "string",
  258. # MSC2758-style namespaced identifier
  259. "minLength": 1,
  260. "maxLength": 255,
  261. "pattern": "^[a-z][a-z0-9_.-]*$",
  262. },
  263. "discover": {"type": "boolean"},
  264. "issuer": {"type": "string"},
  265. "client_id": {"type": "string"},
  266. "client_secret": {"type": "string"},
  267. "client_secret_jwt_key": {
  268. "type": "object",
  269. "required": ["jwt_header"],
  270. "oneOf": [
  271. {"required": ["key"]},
  272. {"required": ["key_file"]},
  273. ],
  274. "properties": {
  275. "key": {"type": "string"},
  276. "key_file": {"type": "string"},
  277. "jwt_header": {
  278. "type": "object",
  279. "required": ["alg"],
  280. "properties": {
  281. "alg": {"type": "string"},
  282. },
  283. "additionalProperties": {"type": "string"},
  284. },
  285. "jwt_payload": {
  286. "type": "object",
  287. "additionalProperties": {"type": "string"},
  288. },
  289. },
  290. },
  291. "client_auth_method": {
  292. "type": "string",
  293. # the following list is the same as the keys of
  294. # authlib.oauth2.auth.ClientAuth.DEFAULT_AUTH_METHODS. We inline it
  295. # to avoid importing authlib here.
  296. "enum": ["client_secret_basic", "client_secret_post", "none"],
  297. },
  298. "scopes": {"type": "array", "items": {"type": "string"}},
  299. "authorization_endpoint": {"type": "string"},
  300. "token_endpoint": {"type": "string"},
  301. "userinfo_endpoint": {"type": "string"},
  302. "jwks_uri": {"type": "string"},
  303. "skip_verification": {"type": "boolean"},
  304. "user_profile_method": {
  305. "type": "string",
  306. "enum": ["auto", "userinfo_endpoint"],
  307. },
  308. "allow_existing_users": {"type": "boolean"},
  309. "user_mapping_provider": {"type": ["object", "null"]},
  310. },
  311. }
  312. # the same as OIDC_PROVIDER_CONFIG_SCHEMA, but with compulsory idp_id and idp_name
  313. OIDC_PROVIDER_CONFIG_WITH_ID_SCHEMA = {
  314. "allOf": [OIDC_PROVIDER_CONFIG_SCHEMA, {"required": ["idp_id", "idp_name"]}]
  315. }
  316. # the `oidc_providers` list can either be None (as it is in the default config), or
  317. # a list of provider configs, each of which requires an explicit ID and name.
  318. OIDC_PROVIDER_LIST_SCHEMA = {
  319. "oneOf": [
  320. {"type": "null"},
  321. {"type": "array", "items": OIDC_PROVIDER_CONFIG_WITH_ID_SCHEMA},
  322. ]
  323. }
  324. # the `oidc_config` setting can either be None (which it used to be in the default
  325. # config), or an object. If an object, it is ignored unless it has an "enabled: True"
  326. # property.
  327. #
  328. # It's *possible* to represent this with jsonschema, but the resultant errors aren't
  329. # particularly clear, so we just check for either an object or a null here, and do
  330. # additional checks in the code.
  331. OIDC_CONFIG_SCHEMA = {"oneOf": [{"type": "null"}, {"type": "object"}]}
  332. # the top-level schema can contain an "oidc_config" and/or an "oidc_providers".
  333. MAIN_CONFIG_SCHEMA = {
  334. "type": "object",
  335. "properties": {
  336. "oidc_config": OIDC_CONFIG_SCHEMA,
  337. "oidc_providers": OIDC_PROVIDER_LIST_SCHEMA,
  338. },
  339. }
  340. def _parse_oidc_provider_configs(config: JsonDict) -> Iterable["OidcProviderConfig"]:
  341. """extract and parse the OIDC provider configs from the config dict
  342. The configuration may contain either a single `oidc_config` object with an
  343. `enabled: True` property, or a list of provider configurations under
  344. `oidc_providers`, *or both*.
  345. Returns a generator which yields the OidcProviderConfig objects
  346. """
  347. validate_config(MAIN_CONFIG_SCHEMA, config, ())
  348. for i, p in enumerate(config.get("oidc_providers") or []):
  349. yield _parse_oidc_config_dict(p, ("oidc_providers", "<item %i>" % (i,)))
  350. # for backwards-compatibility, it is also possible to provide a single "oidc_config"
  351. # object with an "enabled: True" property.
  352. oidc_config = config.get("oidc_config")
  353. if oidc_config and oidc_config.get("enabled", False):
  354. # MAIN_CONFIG_SCHEMA checks that `oidc_config` is an object, but not that
  355. # it matches OIDC_PROVIDER_CONFIG_SCHEMA (see the comments on OIDC_CONFIG_SCHEMA
  356. # above), so now we need to validate it.
  357. validate_config(OIDC_PROVIDER_CONFIG_SCHEMA, oidc_config, ("oidc_config",))
  358. yield _parse_oidc_config_dict(oidc_config, ("oidc_config",))
  359. def _parse_oidc_config_dict(
  360. oidc_config: JsonDict, config_path: Tuple[str, ...]
  361. ) -> "OidcProviderConfig":
  362. """Take the configuration dict and parse it into an OidcProviderConfig
  363. Raises:
  364. ConfigError if the configuration is malformed.
  365. """
  366. ump_config = oidc_config.get("user_mapping_provider", {})
  367. ump_config.setdefault("module", DEFAULT_USER_MAPPING_PROVIDER)
  368. ump_config.setdefault("config", {})
  369. (
  370. user_mapping_provider_class,
  371. user_mapping_provider_config,
  372. ) = load_module(ump_config, config_path + ("user_mapping_provider",))
  373. # Ensure loaded user mapping module has defined all necessary methods
  374. required_methods = [
  375. "get_remote_user_id",
  376. "map_user_attributes",
  377. ]
  378. missing_methods = [
  379. method
  380. for method in required_methods
  381. if not hasattr(user_mapping_provider_class, method)
  382. ]
  383. if missing_methods:
  384. raise ConfigError(
  385. "Class %s is missing required "
  386. "methods: %s"
  387. % (
  388. user_mapping_provider_class,
  389. ", ".join(missing_methods),
  390. ),
  391. config_path + ("user_mapping_provider", "module"),
  392. )
  393. idp_id = oidc_config.get("idp_id", "oidc")
  394. # prefix the given IDP with a prefix specific to the SSO mechanism, to avoid
  395. # clashes with other mechs (such as SAML, CAS).
  396. #
  397. # We allow "oidc" as an exception so that people migrating from old-style
  398. # "oidc_config" format (which has long used "oidc" as its idp_id) can migrate to
  399. # a new-style "oidc_providers" entry without changing the idp_id for their provider
  400. # (and thereby invalidating their user_external_ids data).
  401. if idp_id != "oidc":
  402. idp_id = "oidc-" + idp_id
  403. # MSC2858 also specifies that the idp_icon must be a valid MXC uri
  404. idp_icon = oidc_config.get("idp_icon")
  405. if idp_icon is not None:
  406. try:
  407. parse_and_validate_mxc_uri(idp_icon)
  408. except ValueError as e:
  409. raise ConfigError(
  410. "idp_icon must be a valid MXC URI", config_path + ("idp_icon",)
  411. ) from e
  412. client_secret_jwt_key_config = oidc_config.get("client_secret_jwt_key")
  413. client_secret_jwt_key = None # type: Optional[OidcProviderClientSecretJwtKey]
  414. if client_secret_jwt_key_config is not None:
  415. keyfile = client_secret_jwt_key_config.get("key_file")
  416. if keyfile:
  417. key = read_file(keyfile, config_path + ("client_secret_jwt_key",))
  418. else:
  419. key = client_secret_jwt_key_config["key"]
  420. client_secret_jwt_key = OidcProviderClientSecretJwtKey(
  421. key=key,
  422. jwt_header=client_secret_jwt_key_config["jwt_header"],
  423. jwt_payload=client_secret_jwt_key_config.get("jwt_payload", {}),
  424. )
  425. return OidcProviderConfig(
  426. idp_id=idp_id,
  427. idp_name=oidc_config.get("idp_name", "OIDC"),
  428. idp_icon=idp_icon,
  429. idp_brand=oidc_config.get("idp_brand"),
  430. discover=oidc_config.get("discover", True),
  431. issuer=oidc_config["issuer"],
  432. client_id=oidc_config["client_id"],
  433. client_secret=oidc_config.get("client_secret"),
  434. client_secret_jwt_key=client_secret_jwt_key,
  435. client_auth_method=oidc_config.get("client_auth_method", "client_secret_basic"),
  436. scopes=oidc_config.get("scopes", ["openid"]),
  437. authorization_endpoint=oidc_config.get("authorization_endpoint"),
  438. token_endpoint=oidc_config.get("token_endpoint"),
  439. userinfo_endpoint=oidc_config.get("userinfo_endpoint"),
  440. jwks_uri=oidc_config.get("jwks_uri"),
  441. skip_verification=oidc_config.get("skip_verification", False),
  442. user_profile_method=oidc_config.get("user_profile_method", "auto"),
  443. allow_existing_users=oidc_config.get("allow_existing_users", False),
  444. user_mapping_provider_class=user_mapping_provider_class,
  445. user_mapping_provider_config=user_mapping_provider_config,
  446. )
  447. @attr.s(slots=True, frozen=True)
  448. class OidcProviderClientSecretJwtKey:
  449. # a pem-encoded signing key
  450. key = attr.ib(type=str)
  451. # properties to include in the JWT header
  452. jwt_header = attr.ib(type=Mapping[str, str])
  453. # properties to include in the JWT payload.
  454. jwt_payload = attr.ib(type=Mapping[str, str])
  455. @attr.s(slots=True, frozen=True)
  456. class OidcProviderConfig:
  457. # a unique identifier for this identity provider. Used in the 'user_external_ids'
  458. # table, as well as the query/path parameter used in the login protocol.
  459. idp_id = attr.ib(type=str)
  460. # user-facing name for this identity provider.
  461. idp_name = attr.ib(type=str)
  462. # Optional MXC URI for icon for this IdP.
  463. idp_icon = attr.ib(type=Optional[str])
  464. # Optional brand identifier for this IdP.
  465. idp_brand = attr.ib(type=Optional[str])
  466. # whether the OIDC discovery mechanism is used to discover endpoints
  467. discover = attr.ib(type=bool)
  468. # the OIDC issuer. Used to validate tokens and (if discovery is enabled) to
  469. # discover the provider's endpoints.
  470. issuer = attr.ib(type=str)
  471. # oauth2 client id to use
  472. client_id = attr.ib(type=str)
  473. # oauth2 client secret to use. if `None`, use client_secret_jwt_key to generate
  474. # a secret.
  475. client_secret = attr.ib(type=Optional[str])
  476. # key to use to construct a JWT to use as a client secret. May be `None` if
  477. # `client_secret` is set.
  478. client_secret_jwt_key = attr.ib(type=Optional[OidcProviderClientSecretJwtKey])
  479. # auth method to use when exchanging the token.
  480. # Valid values are 'client_secret_basic', 'client_secret_post' and
  481. # 'none'.
  482. client_auth_method = attr.ib(type=str)
  483. # list of scopes to request
  484. scopes = attr.ib(type=Collection[str])
  485. # the oauth2 authorization endpoint. Required if discovery is disabled.
  486. authorization_endpoint = attr.ib(type=Optional[str])
  487. # the oauth2 token endpoint. Required if discovery is disabled.
  488. token_endpoint = attr.ib(type=Optional[str])
  489. # the OIDC userinfo endpoint. Required if discovery is disabled and the
  490. # "openid" scope is not requested.
  491. userinfo_endpoint = attr.ib(type=Optional[str])
  492. # URI where to fetch the JWKS. Required if discovery is disabled and the
  493. # "openid" scope is used.
  494. jwks_uri = attr.ib(type=Optional[str])
  495. # Whether to skip metadata verification
  496. skip_verification = attr.ib(type=bool)
  497. # Whether to fetch the user profile from the userinfo endpoint. Valid
  498. # values are: "auto" or "userinfo_endpoint".
  499. user_profile_method = attr.ib(type=str)
  500. # whether to allow a user logging in via OIDC to match a pre-existing account
  501. # instead of failing
  502. allow_existing_users = attr.ib(type=bool)
  503. # the class of the user mapping provider
  504. user_mapping_provider_class = attr.ib(type=Type)
  505. # the config of the user mapping provider
  506. user_mapping_provider_config = attr.ib()