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

Commit cdd3088

Browse files
AzrenbethAzrenbethbabolivier
authored
Port the Password Auth Providers module interface to the new generic interface (#10548)
Co-authored-by: Azrenbeth <[email protected]> Co-authored-by: Brendan Abolivier <[email protected]>
1 parent 732bbf6 commit cdd3088

13 files changed

+790
-225
lines changed

changelog.d/10548.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Port the Password Auth Providers module interface to the new generic interface.

docs/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
- [Third-party rules callbacks](modules/third_party_rules_callbacks.md)
4444
- [Presence router callbacks](modules/presence_router_callbacks.md)
4545
- [Account validity callbacks](modules/account_validity_callbacks.md)
46+
- [Password auth provider callbacks](modules/password_auth_provider_callbacks.md)
4647
- [Porting a legacy module to the new interface](modules/porting_legacy_module.md)
4748
- [Workers](workers.md)
4849
- [Using `synctl` with Workers](synctl_workers.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# Password auth provider callbacks
2+
3+
Password auth providers offer a way for server administrators to integrate
4+
their Synapse installation with an external authentication system. The callbacks can be
5+
registered by using the Module API's `register_password_auth_provider_callbacks` method.
6+
7+
## Callbacks
8+
9+
### `auth_checkers`
10+
11+
```
12+
auth_checkers: Dict[Tuple[str,Tuple], Callable]
13+
```
14+
15+
A dict mapping from tuples of a login type identifier (such as `m.login.password`) and a
16+
tuple of field names (such as `("password", "secret_thing")`) to authentication checking
17+
callbacks, which should be of the following form:
18+
19+
```python
20+
async def check_auth(
21+
user: str,
22+
login_type: str,
23+
login_dict: "synapse.module_api.JsonDict",
24+
) -> Optional[
25+
Tuple[
26+
str,
27+
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]]
28+
]
29+
]
30+
```
31+
32+
The login type and field names should be provided by the user in the
33+
request to the `/login` API. [The Matrix specification](https://matrix.org/docs/spec/client_server/latest#authentication-types)
34+
defines some types, however user defined ones are also allowed.
35+
36+
The callback is passed the `user` field provided by the client (which might not be in
37+
`@username:server` form), the login type, and a dictionary of login secrets passed by
38+
the client.
39+
40+
If the authentication is successful, the module must return the user's Matrix ID (e.g.
41+
`@alice:example.com`) and optionally a callback to be called with the response to the
42+
`/login` request. If the module doesn't wish to return a callback, it must return `None`
43+
instead.
44+
45+
If the authentication is unsuccessful, the module must return `None`.
46+
47+
### `check_3pid_auth`
48+
49+
```python
50+
async def check_3pid_auth(
51+
medium: str,
52+
address: str,
53+
password: str,
54+
) -> Optional[
55+
Tuple[
56+
str,
57+
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]]
58+
]
59+
]
60+
```
61+
62+
Called when a user attempts to register or log in with a third party identifier,
63+
such as email. It is passed the medium (eg. `email`), an address (eg. `jdoe@example.com`)
64+
and the user's password.
65+
66+
If the authentication is successful, the module must return the user's Matrix ID (e.g.
67+
`@alice:example.com`) and optionally a callback to be called with the response to the `/login` request.
68+
If the module doesn't wish to return a callback, it must return None instead.
69+
70+
If the authentication is unsuccessful, the module must return None.
71+
72+
### `on_logged_out`
73+
74+
```python
75+
async def on_logged_out(
76+
user_id: str,
77+
device_id: Optional[str],
78+
access_token: str
79+
) -> None
80+
```
81+
Called during a logout request for a user. It is passed the qualified user ID, the ID of the
82+
deactivated device (if any: access tokens are occasionally created without an associated
83+
device ID), and the (now deactivated) access token.
84+
85+
## Example
86+
87+
The example module below implements authentication checkers for two different login types:
88+
- `my.login.type`
89+
- Expects a `my_field` field to be sent to `/login`
90+
- Is checked by the method: `self.check_my_login`
91+
- `m.login.password` (defined in [the spec](https://matrix.org/docs/spec/client_server/latest#password-based))
92+
- Expects a `password` field to be sent to `/login`
93+
- Is checked by the method: `self.check_pass`
94+
95+
96+
```python
97+
from typing import Awaitable, Callable, Optional, Tuple
98+
99+
import synapse
100+
from synapse import module_api
101+
102+
103+
class MyAuthProvider:
104+
def __init__(self, config: dict, api: module_api):
105+
106+
self.api = api
107+
108+
self.credentials = {
109+
"bob": "building",
110+
"@scoop:matrix.org": "digging",
111+
}
112+
113+
api.register_password_auth_provider_callbacks(
114+
auth_checkers={
115+
("my.login_type", ("my_field",)): self.check_my_login,
116+
("m.login.password", ("password",)): self.check_pass,
117+
},
118+
)
119+
120+
async def check_my_login(
121+
self,
122+
username: str,
123+
login_type: str,
124+
login_dict: "synapse.module_api.JsonDict",
125+
) -> Optional[
126+
Tuple[
127+
str,
128+
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
129+
]
130+
]:
131+
if login_type != "my.login_type":
132+
return None
133+
134+
if self.credentials.get(username) == login_dict.get("my_field"):
135+
return self.api.get_qualified_user_id(username)
136+
137+
async def check_pass(
138+
self,
139+
username: str,
140+
login_type: str,
141+
login_dict: "synapse.module_api.JsonDict",
142+
) -> Optional[
143+
Tuple[
144+
str,
145+
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
146+
]
147+
]:
148+
if login_type != "m.login.password":
149+
return None
150+
151+
if self.credentials.get(username) == login_dict.get("password"):
152+
return self.api.get_qualified_user_id(username)
153+
```

docs/modules/porting_legacy_module.md

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ should register this resource in its `__init__` method using the `register_web_r
1212
method from the `ModuleApi` class (see [this section](writing_a_module.html#registering-a-web-resource) for
1313
more info).
1414

15+
There is no longer a `get_db_schema_files` callback provided for password auth provider modules. Any
16+
changes to the database should now be made by the module using the module API class.
17+
1518
The module's author should also update any example in the module's configuration to only
1619
use the new `modules` section in Synapse's configuration file (see [this section](index.html#using-modules)
1720
for more info).

docs/password_auth_providers.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
<h2 style="color:red">
2+
This page of the Synapse documentation is now deprecated. For up to date
3+
documentation on setting up or writing a password auth provider module, please see
4+
<a href="modules.md">this page</a>.
5+
</h2>
6+
17
# Password auth provider modules
28

39
Password auth providers offer a way for server administrators to

docs/sample_config.yaml

-28
Original file line numberDiff line numberDiff line change
@@ -2260,34 +2260,6 @@ email:
22602260
#email_validation: "[%(server_name)s] Validate your email"
22612261

22622262

2263-
# Password providers allow homeserver administrators to integrate
2264-
# their Synapse installation with existing authentication methods
2265-
# ex. LDAP, external tokens, etc.
2266-
#
2267-
# For more information and known implementations, please see
2268-
# https://matrix-org.github.io/synapse/latest/password_auth_providers.html
2269-
#
2270-
# Note: instances wishing to use SAML or CAS authentication should
2271-
# instead use the `saml2_config` or `cas_config` options,
2272-
# respectively.
2273-
#
2274-
password_providers:
2275-
# # Example config for an LDAP auth provider
2276-
# - module: "ldap_auth_provider.LdapAuthProvider"
2277-
# config:
2278-
# enabled: true
2279-
# uri: "ldap://ldap.example.com:389"
2280-
# start_tls: true
2281-
# base: "ou=users,dc=example,dc=com"
2282-
# attributes:
2283-
# uid: "cn"
2284-
# mail: "email"
2285-
# name: "givenName"
2286-
# #bind_dn:
2287-
# #bind_password:
2288-
# #filter: "(objectClass=posixAccount)"
2289-
2290-
22912263

22922264
## Push ##
22932265

synapse/app/_base.py

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from synapse.events.presence_router import load_legacy_presence_router
4343
from synapse.events.spamcheck import load_legacy_spam_checkers
4444
from synapse.events.third_party_rules import load_legacy_third_party_event_rules
45+
from synapse.handlers.auth import load_legacy_password_auth_providers
4546
from synapse.logging.context import PreserveLoggingContext
4647
from synapse.metrics.background_process_metrics import wrap_as_background_process
4748
from synapse.metrics.jemalloc import setup_jemalloc_stats
@@ -379,6 +380,7 @@ def run_sighup(*args, **kwargs):
379380
load_legacy_spam_checkers(hs)
380381
load_legacy_third_party_event_rules(hs)
381382
load_legacy_presence_router(hs)
383+
load_legacy_password_auth_providers(hs)
382384

383385
# If we've configured an expiry time for caches, start the background job now.
384386
setup_expire_lru_cache_entries(hs)

synapse/config/password_auth_providers.py

+23-30
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,29 @@ class PasswordAuthProviderConfig(Config):
2525
section = "authproviders"
2626

2727
def read_config(self, config, **kwargs):
28+
"""Parses the old password auth providers config. The config format looks like this:
29+
30+
password_providers:
31+
# Example config for an LDAP auth provider
32+
- module: "ldap_auth_provider.LdapAuthProvider"
33+
config:
34+
enabled: true
35+
uri: "ldap://ldap.example.com:389"
36+
start_tls: true
37+
base: "ou=users,dc=example,dc=com"
38+
attributes:
39+
uid: "cn"
40+
mail: "email"
41+
name: "givenName"
42+
#bind_dn:
43+
#bind_password:
44+
#filter: "(objectClass=posixAccount)"
45+
46+
We expect admins to use modules for this feature (which is why it doesn't appear
47+
in the sample config file), but we want to keep support for it around for a bit
48+
for backwards compatibility.
49+
"""
50+
2851
self.password_providers: List[Tuple[Type, Any]] = []
2952
providers = []
3053

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

5174
self.password_providers.append((provider_class, provider_config))
52-
53-
def generate_config_section(self, **kwargs):
54-
return """\
55-
# Password providers allow homeserver administrators to integrate
56-
# their Synapse installation with existing authentication methods
57-
# ex. LDAP, external tokens, etc.
58-
#
59-
# For more information and known implementations, please see
60-
# https://matrix-org.github.io/synapse/latest/password_auth_providers.html
61-
#
62-
# Note: instances wishing to use SAML or CAS authentication should
63-
# instead use the `saml2_config` or `cas_config` options,
64-
# respectively.
65-
#
66-
password_providers:
67-
# # Example config for an LDAP auth provider
68-
# - module: "ldap_auth_provider.LdapAuthProvider"
69-
# config:
70-
# enabled: true
71-
# uri: "ldap://ldap.example.com:389"
72-
# start_tls: true
73-
# base: "ou=users,dc=example,dc=com"
74-
# attributes:
75-
# uid: "cn"
76-
# mail: "email"
77-
# name: "givenName"
78-
# #bind_dn:
79-
# #bind_password:
80-
# #filter: "(objectClass=posixAccount)"
81-
"""

0 commit comments

Comments
 (0)