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 17 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.
1 change: 1 addition & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
- [Third-party rules callbacks](modules/third_party_rules_callbacks.md)
- [Presence router callbacks](modules/presence_router_callbacks.md)
- [Account validity callbacks](modules/account_validity_callbacks.md)
- [Password auth provider callbacks](modules/password_auth_provider_callbacks.md)
- [Porting a legacy module to the new interface](modules/porting_legacy_module.md)
- [Workers](workers.md)
- [Using `synctl` with Workers](synctl_workers.md)
Expand Down
155 changes: 155 additions & 0 deletions docs/modules/password_auth_provider_callbacks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Password auth provider callbacks

Password auth providers offer a way for server administrators to integrate
their Synapse installation with an external authentication system. The callbacks can be
registered by using the Module API's `register_password_auth_provider_callbacks` method.

## Callbacks

### `auth_checkers`

```
auth_checkers: Dict[Tuple[str,Tuple], CHECK_AUTH_CALLBACK]
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
```

A dict mapping from tuples of a login type identifier (such as `m.login.password`) and a
tuple of field names (such as `("password", "secret_thing")`) to authentication checking
callbacks.

The login type and field names are the things that should be provided by the user in their
request to the `/login` API.

For example, if a module implements authentication checkers for two different login types:
- `com.example.custom_login`
- Expects `secret1` and `secret2` fields to be sent to `/login`
- Is checked by the method: `self.custom_login_check`
- `m.login.password`
- Expects a `password` field to be sent to `/login`
- Is checked by the method: `self.password_checker`

Then it should register the following dict:

```python
{
("com.example.custom_login", ("secret1", "secret2")): self.check_custom_login,
("m.login.password", ("password",)): self.password_checker,
}
```
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved

An authentication checking method should be of the following form:

```python
async def check_auth(
username: str,
login_type: str,
login_dict: "synapse.module_api.JsonDict",
) -> Optional[
Tuple[
str,
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]]
]
]
```

It is passed the user field provided by the client (which might not be in `@username:server` form),
babolivier marked this conversation as resolved.
Show resolved Hide resolved
the login type, and a dictionary of login secrets passed by the client.

If the authentication is successful, the module must return the user's Matrix ID (e.g. @alice:example.com) and optionally a callback to be called with the response to the `/login` request. If the module doesn't wish to return a callback, it must return None instead.

If the authentication is unsuccessful, the module must return None.
babolivier marked this conversation as resolved.
Show resolved Hide resolved

### `check_3pid_auth`

```python
async def check_3pid_auth(
medium: str,
address: str,
password: str,
) -> Optional[
Tuple[
str,
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]]
]
]
```

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

If the authentication is successful, the module must return the user's Matrix ID (e.g. @alice:example.com) and optionally a callback to be called with the response to the `/login` request. If the module doesn't wish to return a callback, it must return None instead.
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved

If the authentication is unsuccessful, the module must return None.

### `on_logged_out`

```python
async def on_logged_out(
user_id: str,
device_id: Optional[str],
access_token: str
) -> None
```
Called during a logout request for a user. It is passed the qualified user ID, the ID of the
deactivated device (if any: access tokens are occasionally created without an associated
device ID), and the (now deactivated) access token.

## Example
Azrenbeth marked this conversation as resolved.
Show resolved Hide resolved
```python
from typing import Awaitable, Callable, Optional, Tuple

import synapse
from synapse import module_api


class MyAuthProvider:
def __init__(self, config: dict, api: module_api):

self.api = api

self.credentials = {
"bob": "building",
"@scoop:matrix.org": "digging",
}

api.register_password_auth_provider_callbacks(
auth_checkers={
("my.login_type", ("my_field",)): self.check_my_login,
("m.login.password", ("password",)): self.check_pass,
},
)

async def check_my_login(
self,
username: str,
login_type: str,
login_dict: "synapse.module_api.JsonDict",
) -> Optional[
Tuple[
str,
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
]
]:
if login_type != "my.login_type":
return None

if self.credentials.get(username) == login_dict.get("my_field"):
return self.api.get_qualified_user_id(username)

async def check_pass(
self,
username: str,
login_type: str,
login_dict: "synapse.module_api.JsonDict",
) -> Optional[
Tuple[
str,
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
]
]:
if login_type != "m.login.password":
return None

if self.credentials.get(username) == login_dict.get("password"):
return self.api.get_qualified_user_id(username)
```
3 changes: 3 additions & 0 deletions docs/modules/porting_legacy_module.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ should register this resource in its `__init__` method using the `register_web_r
method from the `ModuleApi` class (see [this section](writing_a_module.html#registering-a-web-resource) for
more info).

There is no longer a `get_db_schema_files` callback provided for password auth provider modules. Any
changes to the database should now be made by the module using the module API class.

The module's author should also update any example in the module's configuration to only
use the new `modules` section in Synapse's configuration file (see [this section](index.html#using-modules)
for more info).
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 @@ -2260,34 +2260,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
2 changes: 2 additions & 0 deletions synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,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_providers
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 @@ -379,6 +380,7 @@ def run_sighup(*args, **kwargs):
load_legacy_spam_checkers(hs)
load_legacy_third_party_event_rules(hs)
load_legacy_presence_router(hs)
load_legacy_password_auth_providers(hs)

# 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 password auth providers config. The config format looks like this:

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