mirror of
https://github.com/element-hq/synapse.git
synced 2024-11-25 02:55:46 +03:00
Cleanups to the OpenID Connect integration (#7628)
docs, default configs, comments. Nothing very significant.
This commit is contained in:
parent
e91abfd291
commit
11de843626
8 changed files with 421 additions and 317 deletions
1
changelog.d/7628.misc
Normal file
1
changelog.d/7628.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Minor cleanups to OpenID Connect integration.
|
175
docs/dev/oidc.md
175
docs/dev/oidc.md
|
@ -1,175 +0,0 @@
|
||||||
# How to test OpenID Connect
|
|
||||||
|
|
||||||
Any OpenID Connect Provider (OP) should work with Synapse, as long as it supports the authorization code flow.
|
|
||||||
There are a few options for that:
|
|
||||||
|
|
||||||
- start a local OP. Synapse has been tested with [Hydra][hydra] and [Dex][dex-idp].
|
|
||||||
Note that for an OP to work, it should be served under a secure (HTTPS) origin.
|
|
||||||
A certificate signed with a self-signed, locally trusted CA should work. In that case, start Synapse with a `SSL_CERT_FILE` environment variable set to the path of the CA.
|
|
||||||
- use a publicly available OP. Synapse has been tested with [Google][google-idp].
|
|
||||||
- setup a SaaS OP, like [Auth0][auth0] and [Okta][okta]. Auth0 has a free tier which has been tested with Synapse.
|
|
||||||
|
|
||||||
[google-idp]: https://developers.google.com/identity/protocols/OpenIDConnect#authenticatingtheuser
|
|
||||||
[auth0]: https://auth0.com/
|
|
||||||
[okta]: https://www.okta.com/
|
|
||||||
[dex-idp]: https://github.com/dexidp/dex
|
|
||||||
[hydra]: https://www.ory.sh/docs/hydra/
|
|
||||||
|
|
||||||
|
|
||||||
## Sample configs
|
|
||||||
|
|
||||||
Here are a few configs for providers that should work with Synapse.
|
|
||||||
|
|
||||||
### [Dex][dex-idp]
|
|
||||||
|
|
||||||
[Dex][dex-idp] is a simple, open-source, certified OpenID Connect Provider.
|
|
||||||
Although it is designed to help building a full-blown provider, with some external database, it can be configured with static passwords in a config file.
|
|
||||||
|
|
||||||
Follow the [Getting Started guide](https://github.com/dexidp/dex/blob/master/Documentation/getting-started.md) to install Dex.
|
|
||||||
|
|
||||||
Edit `examples/config-dev.yaml` config file from the Dex repo to add a client:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
staticClients:
|
|
||||||
- id: synapse
|
|
||||||
secret: secret
|
|
||||||
redirectURIs:
|
|
||||||
- '[synapse base url]/_synapse/oidc/callback'
|
|
||||||
name: 'Synapse'
|
|
||||||
```
|
|
||||||
|
|
||||||
Run with `dex serve examples/config-dex.yaml`
|
|
||||||
|
|
||||||
Synapse config:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
oidc_config:
|
|
||||||
enabled: true
|
|
||||||
skip_verification: true # This is needed as Dex is served on an insecure endpoint
|
|
||||||
issuer: "http://127.0.0.1:5556/dex"
|
|
||||||
discover: true
|
|
||||||
client_id: "synapse"
|
|
||||||
client_secret: "secret"
|
|
||||||
scopes:
|
|
||||||
- openid
|
|
||||||
- profile
|
|
||||||
user_mapping_provider:
|
|
||||||
config:
|
|
||||||
localpart_template: '{{ user.name }}'
|
|
||||||
display_name_template: '{{ user.name|capitalize }}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### [Auth0][auth0]
|
|
||||||
|
|
||||||
1. Create a regular web application for Synapse
|
|
||||||
2. Set the Allowed Callback URLs to `[synapse base url]/_synapse/oidc/callback`
|
|
||||||
3. Add a rule to add the `preferred_username` claim.
|
|
||||||
<details>
|
|
||||||
<summary>Code sample</summary>
|
|
||||||
|
|
||||||
```js
|
|
||||||
function addPersistenceAttribute(user, context, callback) {
|
|
||||||
user.user_metadata = user.user_metadata || {};
|
|
||||||
user.user_metadata.preferred_username = user.user_metadata.preferred_username || user.user_id;
|
|
||||||
context.idToken.preferred_username = user.user_metadata.preferred_username;
|
|
||||||
|
|
||||||
auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
|
|
||||||
.then(function(){
|
|
||||||
callback(null, user, context);
|
|
||||||
})
|
|
||||||
.catch(function(err){
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
oidc_config:
|
|
||||||
enabled: true
|
|
||||||
issuer: "https://your-tier.eu.auth0.com/" # TO BE FILLED
|
|
||||||
discover: true
|
|
||||||
client_id: "your-client-id" # TO BE FILLED
|
|
||||||
client_secret: "your-client-secret" # TO BE FILLED
|
|
||||||
scopes:
|
|
||||||
- openid
|
|
||||||
- profile
|
|
||||||
user_mapping_provider:
|
|
||||||
config:
|
|
||||||
localpart_template: '{{ user.preferred_username }}'
|
|
||||||
display_name_template: '{{ user.name }}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### GitHub
|
|
||||||
|
|
||||||
GitHub is a bit special as it is not an OpenID Connect compliant provider, but just a regular OAuth2 provider.
|
|
||||||
The `/user` API endpoint can be used to retrieve informations from the user.
|
|
||||||
As the OIDC login mechanism needs an attribute to uniquely identify users and that endpoint does not return a `sub` property, an alternative `subject_claim` has to be set.
|
|
||||||
|
|
||||||
1. Create a new OAuth application: https://github.com/settings/applications/new
|
|
||||||
2. Set the callback URL to `[synapse base url]/_synapse/oidc/callback`
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
oidc_config:
|
|
||||||
enabled: true
|
|
||||||
issuer: "https://github.com/"
|
|
||||||
discover: false
|
|
||||||
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 }}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Google
|
|
||||||
|
|
||||||
1. Setup a project in the Google API Console
|
|
||||||
2. Obtain the OAuth 2.0 credentials (see <https://developers.google.com/identity/protocols/oauth2/openid-connect>)
|
|
||||||
3. Add this Authorized redirect URI: `[synapse base url]/_synapse/oidc/callback`
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
oidc_config:
|
|
||||||
enabled: true
|
|
||||||
issuer: "https://accounts.google.com/"
|
|
||||||
discover: true
|
|
||||||
client_id: "your-client-id" # TO BE FILLED
|
|
||||||
client_secret: "your-client-secret" # TO BE FILLED
|
|
||||||
scopes:
|
|
||||||
- openid
|
|
||||||
- profile
|
|
||||||
user_mapping_provider:
|
|
||||||
config:
|
|
||||||
localpart_template: '{{ user.given_name|lower }}'
|
|
||||||
display_name_template: '{{ user.name }}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Twitch
|
|
||||||
|
|
||||||
1. Setup a developer account on [Twitch](https://dev.twitch.tv/)
|
|
||||||
2. Obtain the OAuth 2.0 credentials by [creating an app](https://dev.twitch.tv/console/apps/)
|
|
||||||
3. Add this OAuth Redirect URL: `[synapse base url]/_synapse/oidc/callback`
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
oidc_config:
|
|
||||||
enabled: true
|
|
||||||
issuer: "https://id.twitch.tv/oauth2/"
|
|
||||||
discover: true
|
|
||||||
client_id: "your-client-id" # TO BE FILLED
|
|
||||||
client_secret: "your-client-secret" # TO BE FILLED
|
|
||||||
client_auth_method: "client_secret_post"
|
|
||||||
scopes:
|
|
||||||
- openid
|
|
||||||
user_mapping_provider:
|
|
||||||
config:
|
|
||||||
localpart_template: '{{ user.preferred_username }}'
|
|
||||||
display_name_template: '{{ user.name }}'
|
|
||||||
```
|
|
206
docs/openid.md
Normal file
206
docs/openid.md
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
# Configuring Synapse to authenticate against an OpenID Connect provider
|
||||||
|
|
||||||
|
Synapse can be configured to use an OpenID Connect Provider (OP) for
|
||||||
|
authentication, instead of its own local password database.
|
||||||
|
|
||||||
|
Any OP should work with Synapse, as long as it supports the authorization code
|
||||||
|
flow. There are a few options for that:
|
||||||
|
|
||||||
|
- start a local OP. Synapse has been tested with [Hydra][hydra] and
|
||||||
|
[Dex][dex-idp]. Note that for an OP to work, it should be served under a
|
||||||
|
secure (HTTPS) origin. A certificate signed with a self-signed, locally
|
||||||
|
trusted CA should work. In that case, start Synapse with a `SSL_CERT_FILE`
|
||||||
|
environment variable set to the path of the CA.
|
||||||
|
|
||||||
|
- set up a SaaS OP, like [Google][google-idp], [Auth0][auth0] or
|
||||||
|
[Okta][okta]. Synapse has been tested with Auth0 and Google.
|
||||||
|
|
||||||
|
It may also be possible to use other OAuth2 providers which provide the
|
||||||
|
[authorization code grant type](https://tools.ietf.org/html/rfc6749#section-4.1),
|
||||||
|
such as [Github][github-idp].
|
||||||
|
|
||||||
|
[google-idp]: https://developers.google.com/identity/protocols/oauth2/openid-connect
|
||||||
|
[auth0]: https://auth0.com/
|
||||||
|
[okta]: https://www.okta.com/
|
||||||
|
[dex-idp]: https://github.com/dexidp/dex
|
||||||
|
[hydra]: https://www.ory.sh/docs/hydra/
|
||||||
|
[github-idp]: https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps
|
||||||
|
|
||||||
|
## Preparing Synapse
|
||||||
|
|
||||||
|
The OpenID integration in Synapse uses the
|
||||||
|
[`authlib`](https://pypi.org/project/Authlib/) library, which must be installed
|
||||||
|
as follows:
|
||||||
|
|
||||||
|
* The relevant libraries are included in the Docker images and Debian packages
|
||||||
|
provided by `matrix.org` so no further action is needed.
|
||||||
|
|
||||||
|
* If you installed Synapse into a virtualenv, run `/path/to/env/bin/pip
|
||||||
|
install synapse[oidc]` to install the necessary dependencies.
|
||||||
|
|
||||||
|
* For other installation mechanisms, see the documentation provided by the
|
||||||
|
maintainer.
|
||||||
|
|
||||||
|
To enable the OpenID integration, you should then add an `oidc_config` section
|
||||||
|
to your configuration file (or uncomment the `enabled: true` line in the
|
||||||
|
existing section). See [sample_config.yaml](./sample_config.yaml) for some
|
||||||
|
sample settings, as well as the text below for example configurations for
|
||||||
|
specific providers.
|
||||||
|
|
||||||
|
## Sample configs
|
||||||
|
|
||||||
|
Here are a few configs for providers that should work with Synapse.
|
||||||
|
|
||||||
|
### [Dex][dex-idp]
|
||||||
|
|
||||||
|
[Dex][dex-idp] is a simple, open-source, certified OpenID Connect Provider.
|
||||||
|
Although it is designed to help building a full-blown provider with an
|
||||||
|
external database, it can be configured with static passwords in a config file.
|
||||||
|
|
||||||
|
Follow the [Getting Started
|
||||||
|
guide](https://github.com/dexidp/dex/blob/master/Documentation/getting-started.md)
|
||||||
|
to install Dex.
|
||||||
|
|
||||||
|
Edit `examples/config-dev.yaml` config file from the Dex repo to add a client:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
staticClients:
|
||||||
|
- id: synapse
|
||||||
|
secret: secret
|
||||||
|
redirectURIs:
|
||||||
|
- '[synapse public baseurl]/_synapse/oidc/callback'
|
||||||
|
name: 'Synapse'
|
||||||
|
```
|
||||||
|
|
||||||
|
Run with `dex serve examples/config-dex.yaml`.
|
||||||
|
|
||||||
|
Synapse config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
oidc_config:
|
||||||
|
enabled: true
|
||||||
|
skip_verification: true # This is needed as Dex is served on an insecure endpoint
|
||||||
|
issuer: "http://127.0.0.1:5556/dex"
|
||||||
|
client_id: "synapse"
|
||||||
|
client_secret: "secret"
|
||||||
|
scopes: ["openid", "profile"]
|
||||||
|
user_mapping_provider:
|
||||||
|
config:
|
||||||
|
localpart_template: "{{ user.name }}"
|
||||||
|
display_name_template: "{{ user.name|capitalize }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### [Auth0][auth0]
|
||||||
|
|
||||||
|
1. Create a regular web application for Synapse
|
||||||
|
2. Set the Allowed Callback URLs to `[synapse public baseurl]/_synapse/oidc/callback`
|
||||||
|
3. Add a rule to add the `preferred_username` claim.
|
||||||
|
<details>
|
||||||
|
<summary>Code sample</summary>
|
||||||
|
|
||||||
|
```js
|
||||||
|
function addPersistenceAttribute(user, context, callback) {
|
||||||
|
user.user_metadata = user.user_metadata || {};
|
||||||
|
user.user_metadata.preferred_username = user.user_metadata.preferred_username || user.user_id;
|
||||||
|
context.idToken.preferred_username = user.user_metadata.preferred_username;
|
||||||
|
|
||||||
|
auth0.users.updateUserMetadata(user.user_id, user.user_metadata)
|
||||||
|
.then(function(){
|
||||||
|
callback(null, user, context);
|
||||||
|
})
|
||||||
|
.catch(function(err){
|
||||||
|
callback(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Synapse config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
oidc_config:
|
||||||
|
enabled: true
|
||||||
|
issuer: "https://your-tier.eu.auth0.com/" # TO BE FILLED
|
||||||
|
client_id: "your-client-id" # TO BE FILLED
|
||||||
|
client_secret: "your-client-secret" # TO BE FILLED
|
||||||
|
scopes: ["openid", "profile"]
|
||||||
|
user_mapping_provider:
|
||||||
|
config:
|
||||||
|
localpart_template: "{{ user.preferred_username }}"
|
||||||
|
display_name_template: "{{ user.name }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### GitHub
|
||||||
|
|
||||||
|
GitHub is a bit special as it is not an OpenID Connect compliant provider, but
|
||||||
|
just a regular OAuth2 provider.
|
||||||
|
|
||||||
|
The [`/user` API endpoint](https://developer.github.com/v3/users/#get-the-authenticated-user)
|
||||||
|
can be used to retrieve information on the authenticated user. As the Synaspse
|
||||||
|
login mechanism needs an attribute to uniquely identify users, and that endpoint
|
||||||
|
does not return a `sub` property, an alternative `subject_claim` has to be set.
|
||||||
|
|
||||||
|
1. Create a new OAuth application: https://github.com/settings/applications/new.
|
||||||
|
2. Set the callback URL to `[synapse public baseurl]/_synapse/oidc/callback`.
|
||||||
|
|
||||||
|
Synapse config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
oidc_config:
|
||||||
|
enabled: true
|
||||||
|
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 }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### [Google][google-idp]
|
||||||
|
|
||||||
|
1. Set up a project in the Google API Console (see
|
||||||
|
https://developers.google.com/identity/protocols/oauth2/openid-connect#appsetup).
|
||||||
|
2. add an "OAuth Client ID" for a Web Application under "Credentials".
|
||||||
|
3. Copy the Client ID and Client Secret, and add the following to your synapse config:
|
||||||
|
```yaml
|
||||||
|
oidc_config:
|
||||||
|
enabled: true
|
||||||
|
issuer: "https://accounts.google.com/"
|
||||||
|
client_id: "your-client-id" # TO BE FILLED
|
||||||
|
client_secret: "your-client-secret" # TO BE FILLED
|
||||||
|
scopes: ["openid", "profile"]
|
||||||
|
user_mapping_provider:
|
||||||
|
config:
|
||||||
|
localpart_template: "{{ user.given_name|lower }}"
|
||||||
|
display_name_template: "{{ user.name }}"
|
||||||
|
```
|
||||||
|
4. Back in the Google console, add this Authorized redirect URI: `[synapse
|
||||||
|
public baseurl]/_synapse/oidc/callback`.
|
||||||
|
|
||||||
|
### Twitch
|
||||||
|
|
||||||
|
1. Setup a developer account on [Twitch](https://dev.twitch.tv/)
|
||||||
|
2. Obtain the OAuth 2.0 credentials by [creating an app](https://dev.twitch.tv/console/apps/)
|
||||||
|
3. Add this OAuth Redirect URL: `[synapse public baseurl]/_synapse/oidc/callback`
|
||||||
|
|
||||||
|
Synapse config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
oidc_config:
|
||||||
|
enabled: true
|
||||||
|
issuer: "https://id.twitch.tv/oauth2/"
|
||||||
|
client_id: "your-client-id" # TO BE FILLED
|
||||||
|
client_secret: "your-client-secret" # TO BE FILLED
|
||||||
|
client_auth_method: "client_secret_post"
|
||||||
|
user_mapping_provider:
|
||||||
|
config:
|
||||||
|
localpart_template: '{{ user.preferred_username }}'
|
||||||
|
display_name_template: '{{ user.name }}'
|
||||||
|
```
|
|
@ -1379,6 +1379,8 @@ trusted_key_servers:
|
||||||
#key_server_signing_keys_path: "key_server_signing_keys.key"
|
#key_server_signing_keys_path: "key_server_signing_keys.key"
|
||||||
|
|
||||||
|
|
||||||
|
## Single sign-on integration ##
|
||||||
|
|
||||||
# Enable SAML2 for registration and login. Uses pysaml2.
|
# Enable SAML2 for registration and login. Uses pysaml2.
|
||||||
#
|
#
|
||||||
# At least one of `sp_config` or `config_path` must be set in this section to
|
# At least one of `sp_config` or `config_path` must be set in this section to
|
||||||
|
@ -1526,37 +1528,52 @@ saml2_config:
|
||||||
#template_dir: "res/templates"
|
#template_dir: "res/templates"
|
||||||
|
|
||||||
|
|
||||||
# Enable OpenID Connect for registration and login. Uses authlib.
|
# 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/openid.md.
|
||||||
#
|
#
|
||||||
oidc_config:
|
oidc_config:
|
||||||
# enable OpenID Connect. Defaults to false.
|
# Uncomment the following to enable authorization against an OpenID Connect
|
||||||
|
# server. Defaults to false.
|
||||||
#
|
#
|
||||||
#enabled: true
|
#enabled: true
|
||||||
|
|
||||||
# use the OIDC discovery mechanism to discover endpoints. Defaults to true.
|
# Uncomment the following to disable use of the OIDC discovery mechanism to
|
||||||
|
# discover endpoints. Defaults to true.
|
||||||
#
|
#
|
||||||
#discover: true
|
#discover: false
|
||||||
|
|
||||||
# the OIDC issuer. Used to validate tokens and discover the providers endpoints. Required.
|
# 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/"
|
#issuer: "https://accounts.example.com/"
|
||||||
|
|
||||||
# oauth2 client id to use. Required.
|
# oauth2 client id to use.
|
||||||
|
#
|
||||||
|
# Required if 'enabled' is true.
|
||||||
#
|
#
|
||||||
#client_id: "provided-by-your-issuer"
|
#client_id: "provided-by-your-issuer"
|
||||||
|
|
||||||
# oauth2 client secret to use. Required.
|
# oauth2 client secret to use.
|
||||||
|
#
|
||||||
|
# Required if 'enabled' is true.
|
||||||
#
|
#
|
||||||
#client_secret: "provided-by-your-issuer"
|
#client_secret: "provided-by-your-issuer"
|
||||||
|
|
||||||
# auth method to use when exchanging the token.
|
# auth method to use when exchanging the token.
|
||||||
# Valid values are "client_secret_basic" (default), "client_secret_post" and "none".
|
# Valid values are 'client_secret_basic' (default), 'client_secret_post' and
|
||||||
|
# 'none'.
|
||||||
#
|
#
|
||||||
#client_auth_method: "client_secret_basic"
|
#client_auth_method: client_secret_post
|
||||||
|
|
||||||
# list of scopes to ask. This should include the "openid" scope. Defaults to ["openid"].
|
# list of scopes to request. This should normally include the "openid" scope.
|
||||||
|
# Defaults to ["openid"].
|
||||||
#
|
#
|
||||||
#scopes: ["openid"]
|
#scopes: ["openid", "profile"]
|
||||||
|
|
||||||
# the oauth2 authorization endpoint. Required if provider discovery is disabled.
|
# the oauth2 authorization endpoint. Required if provider discovery is disabled.
|
||||||
#
|
#
|
||||||
|
@ -1566,20 +1583,23 @@ oidc_config:
|
||||||
#
|
#
|
||||||
#token_endpoint: "https://accounts.example.com/oauth2/token"
|
#token_endpoint: "https://accounts.example.com/oauth2/token"
|
||||||
|
|
||||||
# the OIDC userinfo endpoint. Required if discovery is disabled and the "openid" scope is not asked.
|
# the OIDC userinfo endpoint. Required if discovery is disabled and the
|
||||||
|
# "openid" scope is not requested.
|
||||||
#
|
#
|
||||||
#userinfo_endpoint: "https://accounts.example.com/userinfo"
|
#userinfo_endpoint: "https://accounts.example.com/userinfo"
|
||||||
|
|
||||||
# URI where to fetch the JWKS. Required if discovery is disabled and the "openid" scope is used.
|
# 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"
|
#jwks_uri: "https://accounts.example.com/.well-known/jwks.json"
|
||||||
|
|
||||||
# skip metadata verification. Defaults to false.
|
# Uncomment to skip metadata verification. Defaults to false.
|
||||||
# Use this if you are connecting to a provider that is not OpenID Connect compliant.
|
#
|
||||||
|
# Use this if you are connecting to a provider that is not OpenID Connect
|
||||||
|
# compliant.
|
||||||
# Avoid this in production.
|
# Avoid this in production.
|
||||||
#
|
#
|
||||||
#skip_verification: false
|
#skip_verification: true
|
||||||
|
|
||||||
|
|
||||||
# An external module can be provided here as a custom solution to mapping
|
# An external module can be provided here as a custom solution to mapping
|
||||||
# attributes returned from a OIDC provider onto a matrix user.
|
# attributes returned from a OIDC provider onto a matrix user.
|
||||||
|
@ -1588,16 +1608,17 @@ oidc_config:
|
||||||
# The custom module's class. Uncomment to use a custom module.
|
# The custom module's class. Uncomment to use a custom module.
|
||||||
# Default is 'synapse.handlers.oidc_handler.JinjaOidcMappingProvider'.
|
# Default is 'synapse.handlers.oidc_handler.JinjaOidcMappingProvider'.
|
||||||
#
|
#
|
||||||
|
# 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
|
#module: mapping_provider.OidcMappingProvider
|
||||||
|
|
||||||
# Custom configuration values for the module. Below options are intended
|
# Custom configuration values for the module. This section will be passed as
|
||||||
# for the built-in provider, they should be changed if using a custom
|
# a Python dictionary to the user mapping provider module's `parse_config`
|
||||||
# module. This section will be passed as a Python dictionary to the
|
# method.
|
||||||
# module's `parse_config` method.
|
|
||||||
#
|
#
|
||||||
# Below is the config of the default mapping provider, based on Jinja2
|
# The examples below are intended for the default provider: they should be
|
||||||
# templates. Those templates are used to render user attributes, where the
|
# changed if using a custom provider.
|
||||||
# userinfo object is available through the `user` variable.
|
|
||||||
#
|
#
|
||||||
config:
|
config:
|
||||||
# name of the claim containing a unique identifier for the user.
|
# name of the claim containing a unique identifier for the user.
|
||||||
|
@ -1605,11 +1626,19 @@ oidc_config:
|
||||||
#
|
#
|
||||||
#subject_claim: "sub"
|
#subject_claim: "sub"
|
||||||
|
|
||||||
# Jinja2 template for the localpart of the MXID
|
# 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 }}"
|
localpart_template: "{{ user.preferred_username }}"
|
||||||
|
|
||||||
# Jinja2 template for the display name to set on first login. Optional.
|
# 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 }}"
|
#display_name_template: "{{ user.given_name }} {{ user.last_name }}"
|
||||||
|
|
||||||
|
@ -1626,7 +1655,8 @@ oidc_config:
|
||||||
# # name: value
|
# # name: value
|
||||||
|
|
||||||
|
|
||||||
# Additional settings to use with single-sign on systems such as SAML2 and CAS.
|
# Additional settings to use with single-sign on systems such as OpenID Connect,
|
||||||
|
# SAML2 and CAS.
|
||||||
#
|
#
|
||||||
sso:
|
sso:
|
||||||
# A list of client URLs which are whitelisted so that the user does not
|
# A list of client URLs which are whitelisted so that the user does not
|
||||||
|
|
|
@ -55,7 +55,6 @@ class OIDCConfig(Config):
|
||||||
self.oidc_token_endpoint = oidc_config.get("token_endpoint")
|
self.oidc_token_endpoint = oidc_config.get("token_endpoint")
|
||||||
self.oidc_userinfo_endpoint = oidc_config.get("userinfo_endpoint")
|
self.oidc_userinfo_endpoint = oidc_config.get("userinfo_endpoint")
|
||||||
self.oidc_jwks_uri = oidc_config.get("jwks_uri")
|
self.oidc_jwks_uri = oidc_config.get("jwks_uri")
|
||||||
self.oidc_subject_claim = oidc_config.get("subject_claim", "sub")
|
|
||||||
self.oidc_skip_verification = oidc_config.get("skip_verification", False)
|
self.oidc_skip_verification = oidc_config.get("skip_verification", False)
|
||||||
|
|
||||||
ump_config = oidc_config.get("user_mapping_provider", {})
|
ump_config = oidc_config.get("user_mapping_provider", {})
|
||||||
|
@ -86,37 +85,52 @@ class OIDCConfig(Config):
|
||||||
|
|
||||||
def generate_config_section(self, config_dir_path, server_name, **kwargs):
|
def generate_config_section(self, config_dir_path, server_name, **kwargs):
|
||||||
return """\
|
return """\
|
||||||
# Enable OpenID Connect for registration and login. Uses authlib.
|
# 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/openid.md.
|
||||||
#
|
#
|
||||||
oidc_config:
|
oidc_config:
|
||||||
# enable OpenID Connect. Defaults to false.
|
# Uncomment the following to enable authorization against an OpenID Connect
|
||||||
|
# server. Defaults to false.
|
||||||
#
|
#
|
||||||
#enabled: true
|
#enabled: true
|
||||||
|
|
||||||
# use the OIDC discovery mechanism to discover endpoints. Defaults to true.
|
# Uncomment the following to disable use of the OIDC discovery mechanism to
|
||||||
|
# discover endpoints. Defaults to true.
|
||||||
#
|
#
|
||||||
#discover: true
|
#discover: false
|
||||||
|
|
||||||
# the OIDC issuer. Used to validate tokens and discover the providers endpoints. Required.
|
# 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/"
|
#issuer: "https://accounts.example.com/"
|
||||||
|
|
||||||
# oauth2 client id to use. Required.
|
# oauth2 client id to use.
|
||||||
|
#
|
||||||
|
# Required if 'enabled' is true.
|
||||||
#
|
#
|
||||||
#client_id: "provided-by-your-issuer"
|
#client_id: "provided-by-your-issuer"
|
||||||
|
|
||||||
# oauth2 client secret to use. Required.
|
# oauth2 client secret to use.
|
||||||
|
#
|
||||||
|
# Required if 'enabled' is true.
|
||||||
#
|
#
|
||||||
#client_secret: "provided-by-your-issuer"
|
#client_secret: "provided-by-your-issuer"
|
||||||
|
|
||||||
# auth method to use when exchanging the token.
|
# auth method to use when exchanging the token.
|
||||||
# Valid values are "client_secret_basic" (default), "client_secret_post" and "none".
|
# Valid values are 'client_secret_basic' (default), 'client_secret_post' and
|
||||||
|
# 'none'.
|
||||||
#
|
#
|
||||||
#client_auth_method: "client_secret_basic"
|
#client_auth_method: client_secret_post
|
||||||
|
|
||||||
# list of scopes to ask. This should include the "openid" scope. Defaults to ["openid"].
|
# list of scopes to request. This should normally include the "openid" scope.
|
||||||
|
# Defaults to ["openid"].
|
||||||
#
|
#
|
||||||
#scopes: ["openid"]
|
#scopes: ["openid", "profile"]
|
||||||
|
|
||||||
# the oauth2 authorization endpoint. Required if provider discovery is disabled.
|
# the oauth2 authorization endpoint. Required if provider discovery is disabled.
|
||||||
#
|
#
|
||||||
|
@ -126,20 +140,23 @@ class OIDCConfig(Config):
|
||||||
#
|
#
|
||||||
#token_endpoint: "https://accounts.example.com/oauth2/token"
|
#token_endpoint: "https://accounts.example.com/oauth2/token"
|
||||||
|
|
||||||
# the OIDC userinfo endpoint. Required if discovery is disabled and the "openid" scope is not asked.
|
# the OIDC userinfo endpoint. Required if discovery is disabled and the
|
||||||
|
# "openid" scope is not requested.
|
||||||
#
|
#
|
||||||
#userinfo_endpoint: "https://accounts.example.com/userinfo"
|
#userinfo_endpoint: "https://accounts.example.com/userinfo"
|
||||||
|
|
||||||
# URI where to fetch the JWKS. Required if discovery is disabled and the "openid" scope is used.
|
# 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"
|
#jwks_uri: "https://accounts.example.com/.well-known/jwks.json"
|
||||||
|
|
||||||
# skip metadata verification. Defaults to false.
|
# Uncomment to skip metadata verification. Defaults to false.
|
||||||
# Use this if you are connecting to a provider that is not OpenID Connect compliant.
|
#
|
||||||
|
# Use this if you are connecting to a provider that is not OpenID Connect
|
||||||
|
# compliant.
|
||||||
# Avoid this in production.
|
# Avoid this in production.
|
||||||
#
|
#
|
||||||
#skip_verification: false
|
#skip_verification: true
|
||||||
|
|
||||||
|
|
||||||
# An external module can be provided here as a custom solution to mapping
|
# An external module can be provided here as a custom solution to mapping
|
||||||
# attributes returned from a OIDC provider onto a matrix user.
|
# attributes returned from a OIDC provider onto a matrix user.
|
||||||
|
@ -148,16 +165,17 @@ class OIDCConfig(Config):
|
||||||
# The custom module's class. Uncomment to use a custom module.
|
# The custom module's class. Uncomment to use a custom module.
|
||||||
# Default is {mapping_provider!r}.
|
# 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
|
#module: mapping_provider.OidcMappingProvider
|
||||||
|
|
||||||
# Custom configuration values for the module. Below options are intended
|
# Custom configuration values for the module. This section will be passed as
|
||||||
# for the built-in provider, they should be changed if using a custom
|
# a Python dictionary to the user mapping provider module's `parse_config`
|
||||||
# module. This section will be passed as a Python dictionary to the
|
# method.
|
||||||
# module's `parse_config` method.
|
|
||||||
#
|
#
|
||||||
# Below is the config of the default mapping provider, based on Jinja2
|
# The examples below are intended for the default provider: they should be
|
||||||
# templates. Those templates are used to render user attributes, where the
|
# changed if using a custom provider.
|
||||||
# userinfo object is available through the `user` variable.
|
|
||||||
#
|
#
|
||||||
config:
|
config:
|
||||||
# name of the claim containing a unique identifier for the user.
|
# name of the claim containing a unique identifier for the user.
|
||||||
|
@ -165,11 +183,19 @@ class OIDCConfig(Config):
|
||||||
#
|
#
|
||||||
#subject_claim: "sub"
|
#subject_claim: "sub"
|
||||||
|
|
||||||
# Jinja2 template for the localpart of the MXID
|
# 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 }}}}"
|
localpart_template: "{{{{ user.preferred_username }}}}"
|
||||||
|
|
||||||
# Jinja2 template for the display name to set on first login. Optional.
|
# 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 }}}}"
|
#display_name_template: "{{{{ user.given_name }}}} {{{{ user.last_name }}}}"
|
||||||
""".format(
|
""".format(
|
||||||
|
|
|
@ -218,6 +218,8 @@ class SAML2Config(Config):
|
||||||
|
|
||||||
def generate_config_section(self, config_dir_path, server_name, **kwargs):
|
def generate_config_section(self, config_dir_path, server_name, **kwargs):
|
||||||
return """\
|
return """\
|
||||||
|
## Single sign-on integration ##
|
||||||
|
|
||||||
# Enable SAML2 for registration and login. Uses pysaml2.
|
# Enable SAML2 for registration and login. Uses pysaml2.
|
||||||
#
|
#
|
||||||
# At least one of `sp_config` or `config_path` must be set in this section to
|
# At least one of `sp_config` or `config_path` must be set in this section to
|
||||||
|
|
|
@ -61,7 +61,8 @@ class SSOConfig(Config):
|
||||||
|
|
||||||
def generate_config_section(self, **kwargs):
|
def generate_config_section(self, **kwargs):
|
||||||
return """\
|
return """\
|
||||||
# Additional settings to use with single-sign on systems such as SAML2 and CAS.
|
# Additional settings to use with single-sign on systems such as OpenID Connect,
|
||||||
|
# SAML2 and CAS.
|
||||||
#
|
#
|
||||||
sso:
|
sso:
|
||||||
# A list of client URLs which are whitelisted so that the user does not
|
# A list of client URLs which are whitelisted so that the user does not
|
||||||
|
|
|
@ -37,6 +37,7 @@ from twisted.web.client import readBody
|
||||||
from synapse.config import ConfigError
|
from synapse.config import ConfigError
|
||||||
from synapse.http.server import finish_request
|
from synapse.http.server import finish_request
|
||||||
from synapse.http.site import SynapseRequest
|
from synapse.http.site import SynapseRequest
|
||||||
|
from synapse.logging.context import make_deferred_yieldable
|
||||||
from synapse.push.mailer import load_jinja2_templates
|
from synapse.push.mailer import load_jinja2_templates
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
from synapse.types import UserID, map_username_to_mxid_localpart
|
from synapse.types import UserID, map_username_to_mxid_localpart
|
||||||
|
@ -99,7 +100,6 @@ class OidcHandler:
|
||||||
hs.config.oidc_client_auth_method,
|
hs.config.oidc_client_auth_method,
|
||||||
) # type: ClientAuth
|
) # type: ClientAuth
|
||||||
self._client_auth_method = hs.config.oidc_client_auth_method # type: str
|
self._client_auth_method = hs.config.oidc_client_auth_method # type: str
|
||||||
self._subject_claim = hs.config.oidc_subject_claim
|
|
||||||
self._provider_metadata = OpenIDProviderMetadata(
|
self._provider_metadata = OpenIDProviderMetadata(
|
||||||
issuer=hs.config.oidc_issuer,
|
issuer=hs.config.oidc_issuer,
|
||||||
authorization_endpoint=hs.config.oidc_authorization_endpoint,
|
authorization_endpoint=hs.config.oidc_authorization_endpoint,
|
||||||
|
@ -310,6 +310,10 @@ class OidcHandler:
|
||||||
received in the callback to exchange it for a token. The call uses the
|
received in the callback to exchange it for a token. The call uses the
|
||||||
``ClientAuth`` to authenticate with the client with its ID and secret.
|
``ClientAuth`` to authenticate with the client with its ID and secret.
|
||||||
|
|
||||||
|
See:
|
||||||
|
https://tools.ietf.org/html/rfc6749#section-3.2
|
||||||
|
https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
code: The authorization code we got from the callback.
|
code: The authorization code we got from the callback.
|
||||||
|
|
||||||
|
@ -362,7 +366,7 @@ class OidcHandler:
|
||||||
code=response.code, phrase=response.phrase.decode("utf-8")
|
code=response.code, phrase=response.phrase.decode("utf-8")
|
||||||
)
|
)
|
||||||
|
|
||||||
resp_body = await readBody(response)
|
resp_body = await make_deferred_yieldable(readBody(response))
|
||||||
|
|
||||||
if response.code >= 500:
|
if response.code >= 500:
|
||||||
# In case of a server error, we should first try to decode the body
|
# In case of a server error, we should first try to decode the body
|
||||||
|
@ -484,6 +488,7 @@ class OidcHandler:
|
||||||
claims_params=claims_params,
|
claims_params=claims_params,
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
logger.info("Reloading JWKS after decode error")
|
||||||
jwk_set = await self.load_jwks(force=True) # try reloading the jwks
|
jwk_set = await self.load_jwks(force=True) # try reloading the jwks
|
||||||
claims = jwt.decode(
|
claims = jwt.decode(
|
||||||
token["id_token"],
|
token["id_token"],
|
||||||
|
@ -592,6 +597,9 @@ class OidcHandler:
|
||||||
# The provider might redirect with an error.
|
# The provider might redirect with an error.
|
||||||
# In that case, just display it as-is.
|
# In that case, just display it as-is.
|
||||||
if b"error" in request.args:
|
if b"error" in request.args:
|
||||||
|
# error response from the auth server. see:
|
||||||
|
# https://tools.ietf.org/html/rfc6749#section-4.1.2.1
|
||||||
|
# https://openid.net/specs/openid-connect-core-1_0.html#AuthError
|
||||||
error = request.args[b"error"][0].decode()
|
error = request.args[b"error"][0].decode()
|
||||||
description = request.args.get(b"error_description", [b""])[0].decode()
|
description = request.args.get(b"error_description", [b""])[0].decode()
|
||||||
|
|
||||||
|
@ -605,8 +613,11 @@ class OidcHandler:
|
||||||
self._render_error(request, error, description)
|
self._render_error(request, error, description)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# otherwise, it is presumably a successful response. see:
|
||||||
|
# https://tools.ietf.org/html/rfc6749#section-4.1.2
|
||||||
|
|
||||||
# Fetch the session cookie
|
# Fetch the session cookie
|
||||||
session = request.getCookie(SESSION_COOKIE_NAME)
|
session = request.getCookie(SESSION_COOKIE_NAME) # type: Optional[bytes]
|
||||||
if session is None:
|
if session is None:
|
||||||
logger.info("No session cookie found")
|
logger.info("No session cookie found")
|
||||||
self._render_error(request, "missing_session", "No session cookie found")
|
self._render_error(request, "missing_session", "No session cookie found")
|
||||||
|
@ -654,7 +665,7 @@ class OidcHandler:
|
||||||
self._render_error(request, "invalid_request", "Code parameter is missing")
|
self._render_error(request, "invalid_request", "Code parameter is missing")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info("Exchanging code")
|
logger.debug("Exchanging code")
|
||||||
code = request.args[b"code"][0].decode()
|
code = request.args[b"code"][0].decode()
|
||||||
try:
|
try:
|
||||||
token = await self._exchange_code(code)
|
token = await self._exchange_code(code)
|
||||||
|
@ -663,10 +674,12 @@ class OidcHandler:
|
||||||
self._render_error(request, e.error, e.error_description)
|
self._render_error(request, e.error, e.error_description)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logger.debug("Successfully obtained OAuth2 access token")
|
||||||
|
|
||||||
# Now that we have a token, get the userinfo, either by decoding the
|
# Now that we have a token, get the userinfo, either by decoding the
|
||||||
# `id_token` or by fetching the `userinfo_endpoint`.
|
# `id_token` or by fetching the `userinfo_endpoint`.
|
||||||
if self._uses_userinfo:
|
if self._uses_userinfo:
|
||||||
logger.info("Fetching userinfo")
|
logger.debug("Fetching userinfo")
|
||||||
try:
|
try:
|
||||||
userinfo = await self._fetch_userinfo(token)
|
userinfo = await self._fetch_userinfo(token)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -674,7 +687,7 @@ class OidcHandler:
|
||||||
self._render_error(request, "fetch_error", str(e))
|
self._render_error(request, "fetch_error", str(e))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
logger.info("Extracting userinfo from id_token")
|
logger.debug("Extracting userinfo from id_token")
|
||||||
try:
|
try:
|
||||||
userinfo = await self._parse_id_token(token, nonce=nonce)
|
userinfo = await self._parse_id_token(token, nonce=nonce)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -750,7 +763,7 @@ class OidcHandler:
|
||||||
return macaroon.serialize()
|
return macaroon.serialize()
|
||||||
|
|
||||||
def _verify_oidc_session_token(
|
def _verify_oidc_session_token(
|
||||||
self, session: str, state: str
|
self, session: bytes, state: str
|
||||||
) -> Tuple[str, str, Optional[str]]:
|
) -> Tuple[str, str, Optional[str]]:
|
||||||
"""Verifies and extract an OIDC session token.
|
"""Verifies and extract an OIDC session token.
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue