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

Commit 83434df

Browse files
authored
Update the auth providers to be async. (#7935)
1 parent 7078866 commit 83434df

File tree

4 files changed

+118
-112
lines changed

4 files changed

+118
-112
lines changed

changelog.d/7935.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Convert the auth providers to be async/await.

docs/password_auth_providers.md

Lines changed: 94 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -19,102 +19,103 @@ password auth provider module implementations:
1919

2020
Password auth provider classes must provide the following methods:
2121

22-
*class* `SomeProvider.parse_config`(*config*)
22+
* `parse_config(config)`
23+
This method is passed the `config` object for this module from the
24+
homeserver configuration file.
2325

24-
> This method is passed the `config` object for this module from the
25-
> homeserver configuration file.
26-
>
27-
> It should perform any appropriate sanity checks on the provided
28-
> configuration, and return an object which is then passed into
29-
> `__init__`.
26+
It should perform any appropriate sanity checks on the provided
27+
configuration, and return an object which is then passed into
3028

31-
*class* `SomeProvider`(*config*, *account_handler*)
29+
This method should have the `@staticmethod` decoration.
3230

33-
> The constructor is passed the config object returned by
34-
> `parse_config`, and a `synapse.module_api.ModuleApi` object which
35-
> allows the password provider to check if accounts exist and/or create
36-
> new ones.
31+
* `__init__(self, config, account_handler)`
32+
33+
The constructor is passed the config object returned by
34+
`parse_config`, and a `synapse.module_api.ModuleApi` object which
35+
allows the password provider to check if accounts exist and/or create
36+
new ones.
3737

3838
## Optional methods
3939

40-
Password auth provider classes may optionally provide the following
41-
methods.
42-
43-
*class* `SomeProvider.get_db_schema_files`()
44-
45-
> This method, if implemented, should return an Iterable of
46-
> `(name, stream)` pairs of database schema files. Each file is applied
47-
> in turn at initialisation, and a record is then made in the database
48-
> so that it is not re-applied on the next start.
49-
50-
`someprovider.get_supported_login_types`()
51-
52-
> This method, if implemented, should return a `dict` mapping from a
53-
> login type identifier (such as `m.login.password`) to an iterable
54-
> giving the fields which must be provided by the user in the submission
55-
> to the `/login` api. These fields are passed in the `login_dict`
56-
> dictionary to `check_auth`.
57-
>
58-
> For example, if a password auth provider wants to implement a custom
59-
> login type of `com.example.custom_login`, where the client is expected
60-
> to pass the fields `secret1` and `secret2`, the provider should
61-
> implement this method and return the following dict:
62-
>
63-
> {"com.example.custom_login": ("secret1", "secret2")}
64-
65-
`someprovider.check_auth`(*username*, *login_type*, *login_dict*)
66-
67-
> This method is the one that does the real work. If implemented, it
68-
> will be called for each login attempt where the login type matches one
69-
> of the keys returned by `get_supported_login_types`.
70-
>
71-
> It is passed the (possibly UNqualified) `user` provided by the client,
72-
> the login type, and a dictionary of login secrets passed by the
73-
> client.
74-
>
75-
> The method should return a Twisted `Deferred` object, which resolves
76-
> to the canonical `@localpart:domain` user id if authentication is
77-
> successful, and `None` if not.
78-
>
79-
> Alternatively, the `Deferred` can resolve to a `(str, func)` tuple, in
80-
> which case the second field is a callback which will be called with
81-
> the result from the `/login` call (including `access_token`,
82-
> `device_id`, etc.)
83-
84-
`someprovider.check_3pid_auth`(*medium*, *address*, *password*)
85-
86-
> This method, if implemented, is called when a user attempts to
87-
> register or log in with a third party identifier, such as email. It is
88-
> passed the medium (ex. "email"), an address (ex.
89-
> "<[email protected]>") and the user's password.
90-
>
91-
> The method should return a Twisted `Deferred` object, which resolves
92-
> to a `str` containing the user's (canonical) User ID if
93-
> authentication was successful, and `None` if not.
94-
>
95-
> As with `check_auth`, the `Deferred` may alternatively resolve to a
96-
> `(user_id, callback)` tuple.
97-
98-
`someprovider.check_password`(*user_id*, *password*)
99-
100-
> This method provides a simpler interface than
101-
> `get_supported_login_types` and `check_auth` for password auth
102-
> providers that just want to provide a mechanism for validating
103-
> `m.login.password` logins.
104-
>
105-
> Iif implemented, it will be called to check logins with an
106-
> `m.login.password` login type. It is passed a qualified
107-
> `@localpart:domain` user id, and the password provided by the user.
108-
>
109-
> The method should return a Twisted `Deferred` object, which resolves
110-
> to `True` if authentication is successful, and `False` if not.
111-
112-
`someprovider.on_logged_out`(*user_id*, *device_id*, *access_token*)
113-
114-
> This method, if implemented, is called when a user logs out. It is
115-
> passed the qualified user ID, the ID of the deactivated device (if
116-
> any: access tokens are occasionally created without an associated
117-
> device ID), and the (now deactivated) access token.
118-
>
119-
> It may return a Twisted `Deferred` object; the logout request will
120-
> wait for the deferred to complete but the result is ignored.
40+
Password auth provider classes may optionally provide the following methods:
41+
42+
* `get_db_schema_files(self)`
43+
44+
This method, if implemented, should return an Iterable of
45+
`(name, stream)` pairs of database schema files. Each file is applied
46+
in turn at initialisation, and a record is then made in the database
47+
so that it is not re-applied on the next start.
48+
49+
* `get_supported_login_types(self)`
50+
51+
This method, if implemented, should return a `dict` mapping from a
52+
login type identifier (such as `m.login.password`) to an iterable
53+
giving the fields which must be provided by the user in the submission
54+
to [the `/login` API](https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login).
55+
These fields are passed in the `login_dict` dictionary to `check_auth`.
56+
57+
For example, if a password auth provider wants to implement a custom
58+
login type of `com.example.custom_login`, where the client is expected
59+
to pass the fields `secret1` and `secret2`, the provider should
60+
implement this method and return the following dict:
61+
62+
```python
63+
{"com.example.custom_login": ("secret1", "secret2")}
64+
```
65+
66+
* `check_auth(self, username, login_type, login_dict)`
67+
68+
This method does the real work. If implemented, it
69+
will be called for each login attempt where the login type matches one
70+
of the keys returned by `get_supported_login_types`.
71+
72+
It is passed the (possibly unqualified) `user` field provided by the client,
73+
the login type, and a dictionary of login secrets passed by the
74+
client.
75+
76+
The method should return an `Awaitable` object, which resolves
77+
to the canonical `@localpart:domain` user ID if authentication is
78+
successful, and `None` if not.
79+
80+
Alternatively, the `Awaitable` can resolve to a `(str, func)` tuple, in
81+
which case the second field is a callback which will be called with
82+
the result from the `/login` call (including `access_token`,
83+
`device_id`, etc.)
84+
85+
* `check_3pid_auth(self, medium, address, password)`
86+
87+
This method, if implemented, is called when a user attempts to
88+
register or log in with a third party identifier, such as email. It is
89+
passed the medium (ex. "email"), an address (ex.
90+
"<[email protected]>") and the user's password.
91+
92+
The method should return an `Awaitable` object, which resolves
93+
to a `str` containing the user's (canonical) User id if
94+
authentication was successful, and `None` if not.
95+
96+
As with `check_auth`, the `Awaitable` may alternatively resolve to a
97+
`(user_id, callback)` tuple.
98+
99+
* `check_password(self, user_id, password)`
100+
101+
This method provides a simpler interface than
102+
`get_supported_login_types` and `check_auth` for password auth
103+
providers that just want to provide a mechanism for validating
104+
`m.login.password` logins.
105+
106+
If implemented, it will be called to check logins with an
107+
`m.login.password` login type. It is passed a qualified
108+
`@localpart:domain` user id, and the password provided by the user.
109+
110+
The method should return an `Awaitable` object, which resolves
111+
to `True` if authentication is successful, and `False` if not.
112+
113+
* `on_logged_out(self, user_id, device_id, access_token)`
114+
115+
This method, if implemented, is called when a user logs out. It is
116+
passed the qualified user ID, the ID of the deactivated device (if
117+
any: access tokens are occasionally created without an associated
118+
device ID), and the (now deactivated) access token.
119+
120+
It may return an `Awaitable` object; the logout request will
121+
wait for the `Awaitable` to complete, but the result is ignored.

synapse/handlers/auth.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
16+
import inspect
1617
import logging
1718
import time
1819
import unicodedata
@@ -863,11 +864,15 @@ async def delete_access_token(self, access_token: str):
863864
# see if any of our auth providers want to know about this
864865
for provider in self.password_providers:
865866
if hasattr(provider, "on_logged_out"):
866-
await provider.on_logged_out(
867+
# This might return an awaitable, if it does block the log out
868+
# until it completes.
869+
result = provider.on_logged_out(
867870
user_id=str(user_info["user"]),
868871
device_id=user_info["device_id"],
869872
access_token=access_token,
870873
)
874+
if inspect.isawaitable(result):
875+
await result
871876

872877
# delete pushers associated with this access token
873878
if user_info["token_id"] is not None:

synapse/handlers/ui_auth/checkers.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
# limitations under the License.
1515

1616
import logging
17+
from typing import Any
1718

1819
from canonicaljson import json
1920

20-
from twisted.internet import defer
2121
from twisted.web.client import PartialDownloadError
2222

2323
from synapse.api.constants import LoginType
@@ -33,25 +33,25 @@ class UserInteractiveAuthChecker:
3333
def __init__(self, hs):
3434
pass
3535

36-
def is_enabled(self):
36+
def is_enabled(self) -> bool:
3737
"""Check if the configuration of the homeserver allows this checker to work
3838
3939
Returns:
40-
bool: True if this login type is enabled.
40+
True if this login type is enabled.
4141
"""
4242

43-
def check_auth(self, authdict, clientip):
43+
async def check_auth(self, authdict: dict, clientip: str) -> Any:
4444
"""Given the authentication dict from the client, attempt to check this step
4545
4646
Args:
47-
authdict (dict): authentication dictionary from the client
48-
clientip (str): The IP address of the client.
47+
authdict: authentication dictionary from the client
48+
clientip: The IP address of the client.
4949
5050
Raises:
5151
SynapseError if authentication failed
5252
5353
Returns:
54-
Deferred: the result of authentication (to pass back to the client?)
54+
The result of authentication (to pass back to the client?)
5555
"""
5656
raise NotImplementedError()
5757

@@ -62,8 +62,8 @@ class DummyAuthChecker(UserInteractiveAuthChecker):
6262
def is_enabled(self):
6363
return True
6464

65-
def check_auth(self, authdict, clientip):
66-
return defer.succeed(True)
65+
async def check_auth(self, authdict, clientip):
66+
return True
6767

6868

6969
class TermsAuthChecker(UserInteractiveAuthChecker):
@@ -72,8 +72,8 @@ class TermsAuthChecker(UserInteractiveAuthChecker):
7272
def is_enabled(self):
7373
return True
7474

75-
def check_auth(self, authdict, clientip):
76-
return defer.succeed(True)
75+
async def check_auth(self, authdict, clientip):
76+
return True
7777

7878

7979
class RecaptchaAuthChecker(UserInteractiveAuthChecker):
@@ -89,8 +89,7 @@ def __init__(self, hs):
8989
def is_enabled(self):
9090
return self._enabled
9191

92-
@defer.inlineCallbacks
93-
def check_auth(self, authdict, clientip):
92+
async def check_auth(self, authdict, clientip):
9493
try:
9594
user_response = authdict["response"]
9695
except KeyError:
@@ -107,7 +106,7 @@ def check_auth(self, authdict, clientip):
107106
# TODO: get this from the homeserver rather than creating a new one for
108107
# each request
109108
try:
110-
resp_body = yield self._http_client.post_urlencoded_get_json(
109+
resp_body = await self._http_client.post_urlencoded_get_json(
111110
self._url,
112111
args={
113112
"secret": self._secret,
@@ -219,8 +218,8 @@ def is_enabled(self):
219218
ThreepidBehaviour.LOCAL,
220219
)
221220

222-
def check_auth(self, authdict, clientip):
223-
return defer.ensureDeferred(self._check_threepid("email", authdict))
221+
async def check_auth(self, authdict, clientip):
222+
return await self._check_threepid("email", authdict)
224223

225224

226225
class MsisdnAuthChecker(UserInteractiveAuthChecker, _BaseThreepidAuthChecker):
@@ -233,8 +232,8 @@ def __init__(self, hs):
233232
def is_enabled(self):
234233
return bool(self.hs.config.account_threepid_delegate_msisdn)
235234

236-
def check_auth(self, authdict, clientip):
237-
return defer.ensureDeferred(self._check_threepid("msisdn", authdict))
235+
async def check_auth(self, authdict, clientip):
236+
return await self._check_threepid("msisdn", authdict)
238237

239238

240239
INTERACTIVE_AUTH_CHECKERS = [

0 commit comments

Comments
 (0)