Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Port the Password Auth Providers module interface to the new generic interface #10548

Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0841865
Moved to using new callback registering style
Aug 6, 2021
58d87e9
Added tests for new and legacy modules
Aug 6, 2021
accef11
Removed the old module style from sample_config
Aug 6, 2021
d84e47c
Updated docs with information on new callbacks
Aug 6, 2021
b198647
Added porting instructions to the docs
Aug 6, 2021
d7d89ce
added changelog
Aug 6, 2021
1f87fbd
removed now unused ModuleApi object in auth handler
Aug 6, 2021
6ad6ccd
Added error handling when calling callbacks
Aug 11, 2021
efeb758
auth checkers now MUST return tuple
Aug 11, 2021
e6bfd49
Removed duplicated statement in the docs
Aug 19, 2021
00c11c5
Merged origin/develop into branch
Aug 19, 2021
3083565
Applied suggestions from code review:
Aug 23, 2021
4f18f1b
Register login_type, fields, auth_checker all in one chunk
Aug 23, 2021
f4a8484
Applied suggestions from code review
Aug 25, 2021
e65d49e
Merge remote-tracking branch 'origin/develop' into azren/password_aut…
Sep 9, 2021
3c9b6c2
Reformat password_aut_providers docs and fix some small issues
Sep 9, 2021
e889594
Run linters
Sep 9, 2021
4ad1bd1
Apply suggestion from code review
Sep 20, 2021
8e6ebfb
Merge remote-tracking branch 'origin/develop' into azren/password_aut…
Sep 20, 2021
55eede5
Run linters
Sep 20, 2021
01c65d7
Add link to spec
Sep 20, 2021
6fe825f
Tweaks to documentation
Sep 20, 2021
86e9607
Add typehints to auth.py
Sep 20, 2021
fc72533
Merge branch 'develop' into azren/password_auth_providers_to_new_modu…
babolivier Oct 13, 2021
5e37f11
Apply suggestions from code review
babolivier Oct 13, 2021
1cd658a
Incorporate review comment + cleanup
babolivier Oct 13, 2021
73ff756
Standardise example values
babolivier Oct 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/10548.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Port the Password Auth Providers module interface to the new generic interface.
110 changes: 110 additions & 0 deletions docs/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,99 @@ For example, if the user `@alice:example.org` is passed to this method, and the
`{"@bob:example.com", "@charlie:somewhere.org"}` is returned, this signifies that Alice
should receive presence updates sent by Bob and Charlie, regardless of whether these users
share a room.
#### Password auth provider callbacks
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved

Password auth providers offer a way for server administrators to integrate
their Synapse installation with an existing authentication system. The callbacks can be
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
registered by using the Module API's `register_password_auth_provider_callbacks` method.

To register authentication checkers, the module should register both of:

- `supported_login_types: Dict[str, Iterable[str]]`

A dict mapping from a login type identifier (such as `m.login.password`) to an
iterable giving the fields which must be provided by the user in the submission
to the `/login` API.

For example, if a module wants to implement a custom login type of
`com.example.custom_login`, where the client is expected to pass the fields `secret1`
and `secret2` and an alternative password authenticator for `m.login.password`, then
it should register the following dict:

```python
{
"com.example.custom_login": ("secret1", "secret2"),
"m.login.password": ("password",),
}
```

- `auth_checkers: Dict[str, CHECK_AUTH_CALLBACK]`

A dict mapping from a login type identifier (such as `m.login.password`) to an authentication
checking method of the following form:

```python
async def check_auth(
username: str,
login_type: str,
login_dict: JsonDict
) -> Optional[Tuple[str, Optional[Callable]]]
```

It is passed the (possibly unqualified) user field provided by the client,
the login type, and a dictionary of login secrets passed by the client.

On a failure it should return None. On a success it should return a (user_id, callback) tuple
where user_id is a str containing the user's (canonical) matrix id and the callback is a method
to be called with the result from the `/login` call (see the
<a href="https://spec.matrix.org/unstable/client-server-api/#login">spec</a>
for details). This callback can be None.

So continuing the same example, if the module has two authentication checkers, one for
`com.example.custom_login` named `self.custom_check` and one for `m.login.password` named
`self.password_check` then it should register the following dict:

```python
{
"com.example.custom_login": self.custom_check,
"m.login.password": self.password_check,
}
```

Additionally, the following callbacks are available:

```python
async def check_3pid_auth(
medium: str,
address: str,
password: str,
) -> Optional[Tuple[str, Optional[Callable]]]
```

This is called when a user attempts to register or log in with a third party identifier,
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
such as email. It is passed the medium (eg. "email"), an address (eg. "jdoe@example.com")
and the user's password.

On a failure it should return None. On a success it should return a (user_id, callback) tuple
where user_id is a str containing the user's (canonical) matrix id and the callback is a method
to be called with the result from the `/login` call (see the
<a href="https://spec.matrix.org/unstable/client-server-api/#login">spec</a>
for details). This callback can be None.

```python
async def on_logged_out(
user_id: str,
device_id: str,
access_token: str,
) -> None
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
```
This is called when a user logs out. It is passed the qualified user ID, the ID of the
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
deactivated device (if any: access tokens are occasionally created without an associated
device ID), and the (now deactivated) access token.

The callback must be asynchronous but the logout request will wait for the callback
to complete.
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved


### Porting an existing module that uses the old interface

Expand All @@ -347,6 +440,23 @@ The module's author should also update any example in the module's configuration
use the new `modules` section in Synapse's configuration file (see [this section](#using-modules)
for more info).

#### Porting password auth provider modules

There is no longer a `get_db_schema_files` callback provided, any changes to the database
should now be made by the module using the module API class.

To port a module that has a `check_password` method:
- Register `"m.login.password": ("password",)` as a supported login type
- Register `"m.login.password": self.check_password` as an auth checker
- Set `self.module_api` to point to the `ModuleApi` object given in `__init__`
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
- Change the arguments of `check_password` to
`(username: str, login_type: str, login_dict: JsonDict)`
- Add the following lines to the top of the `check_password` method:
- `user_id = self.module_api.get_qualified_user_id(username)`
- `password = login_dict["password"]`
- Alter `check_password` so that it returns `None` on an authentication failure, and `user_id`
on a success
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved

### Example

The example below is a module that implements the spam checker callback
Expand Down
6 changes: 6 additions & 0 deletions docs/password_auth_providers.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
<h2 style="color:red">
This page of the Synapse documentation is now deprecated. For up to date
documentation on setting up or writing a password auth provider module, please see
<a href="modules.md">this page</a>.
</h2>

# Password auth provider modules

Password auth providers offer a way for server administrators to
Expand Down
28 changes: 0 additions & 28 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2206,34 +2206,6 @@ email:
#email_validation: "[%(server_name)s] Validate your email"


# Password providers allow homeserver administrators to integrate
# their Synapse installation with existing authentication methods
# ex. LDAP, external tokens, etc.
#
# For more information and known implementations, please see
# https://matrix-org.github.io/synapse/latest/password_auth_providers.html
#
# Note: instances wishing to use SAML or CAS authentication should
# instead use the `saml2_config` or `cas_config` options,
# respectively.
#
password_providers:
# # Example config for an LDAP auth provider
# - module: "ldap_auth_provider.LdapAuthProvider"
# config:
# enabled: true
# uri: "ldap://ldap.example.com:389"
# start_tls: true
# base: "ou=users,dc=example,dc=com"
# attributes:
# uid: "cn"
# mail: "email"
# name: "givenName"
# #bind_dn:
# #bind_password:
# #filter: "(objectClass=posixAccount)"



## Push ##

Expand Down
3 changes: 3 additions & 0 deletions synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from synapse.events.presence_router import load_legacy_presence_router
from synapse.events.spamcheck import load_legacy_spam_checkers
from synapse.events.third_party_rules import load_legacy_third_party_event_rules
from synapse.handlers.auth import load_legacy_password_auth_provider
from synapse.logging.context import PreserveLoggingContext
from synapse.metrics.background_process_metrics import wrap_as_background_process
from synapse.metrics.jemalloc import setup_jemalloc_stats
Expand Down Expand Up @@ -372,6 +373,8 @@ def run_sighup(*args, **kwargs):
load_legacy_spam_checkers(hs)
load_legacy_third_party_event_rules(hs)
load_legacy_presence_router(hs)
for module, config in hs.config.password_providers:
load_legacy_password_auth_provider(module=module, config=config, api=module_api)
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved

# If we've configured an expiry time for caches, start the background job now.
setup_expire_lru_cache_entries(hs)
Expand Down
53 changes: 23 additions & 30 deletions synapse/config/password_auth_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,29 @@ class PasswordAuthProviderConfig(Config):
section = "authproviders"

def read_config(self, config, **kwargs):
"""Parses the old account validity config. The config format looks like this:
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved

password_providers:
# Example config for an LDAP auth provider
- module: "ldap_auth_provider.LdapAuthProvider"
config:
enabled: true
uri: "ldap://ldap.example.com:389"
start_tls: true
base: "ou=users,dc=example,dc=com"
attributes:
uid: "cn"
mail: "email"
name: "givenName"
#bind_dn:
#bind_password:
#filter: "(objectClass=posixAccount)"

We expect admins to use modules for this feature (which is why it doesn't appear
in the sample config file), but we want to keep support for it around for a bit
for backwards compatibility.
"""

self.password_providers: List[Any] = []
providers = []

Expand All @@ -49,33 +72,3 @@ def read_config(self, config, **kwargs):
)

self.password_providers.append((provider_class, provider_config))

def generate_config_section(self, **kwargs):
return """\
# Password providers allow homeserver administrators to integrate
# their Synapse installation with existing authentication methods
# ex. LDAP, external tokens, etc.
#
# For more information and known implementations, please see
# https://matrix-org.github.io/synapse/latest/password_auth_providers.html
#
# Note: instances wishing to use SAML or CAS authentication should
# instead use the `saml2_config` or `cas_config` options,
# respectively.
#
password_providers:
# # Example config for an LDAP auth provider
# - module: "ldap_auth_provider.LdapAuthProvider"
# config:
# enabled: true
# uri: "ldap://ldap.example.com:389"
# start_tls: true
# base: "ou=users,dc=example,dc=com"
# attributes:
# uid: "cn"
# mail: "email"
# name: "givenName"
# #bind_dn:
# #bind_password:
# #filter: "(objectClass=posixAccount)"
"""
Loading