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

Commit 36dc154

Browse files
authored
Add a module type for account validity (#9884)
This adds an API for third-party plugin modules to implement account validity, so they can provide this feature instead of Synapse. The module implementing the current behaviour for this feature can be found at https://github.com/matrix-org/synapse-email-account-validity. To allow for a smooth transition between the current feature and the new module, hooks have been added to the existing account validity endpoints to allow their behaviours to be overridden by a module.
1 parent d427f64 commit 36dc154

File tree

13 files changed

+438
-228
lines changed

13 files changed

+438
-228
lines changed

changelog.d/9884.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a module type for the account validity feature.

docs/modules.md

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Modules can register web resources onto Synapse's web server using the following
6363
API method:
6464

6565
```python
66-
def ModuleApi.register_web_resource(path: str, resource: IResource)
66+
def ModuleApi.register_web_resource(path: str, resource: IResource) -> None
6767
```
6868

6969
The path is the full absolute path to register the resource at. For example, if you
@@ -91,12 +91,17 @@ are split in categories. A single module may implement callbacks from multiple c
9191
and is under no obligation to implement all callbacks from the categories it registers
9292
callbacks for.
9393

94+
Modules can register callbacks using one of the module API's `register_[...]_callbacks`
95+
methods. The callback functions are passed to these methods as keyword arguments, with
96+
the callback name as the argument name and the function as its value. This is demonstrated
97+
in the example below. A `register_[...]_callbacks` method exists for each module type
98+
documented in this section.
99+
94100
#### Spam checker callbacks
95101

96-
To register one of the callbacks described in this section, a module needs to use the
97-
module API's `register_spam_checker_callbacks` method. The callback functions are passed
98-
to `register_spam_checker_callbacks` as keyword arguments, with the callback name as the
99-
argument name and the function as its value. This is demonstrated in the example below.
102+
Spam checker callbacks allow module developers to implement spam mitigation actions for
103+
Synapse instances. Spam checker callbacks can be registered using the module API's
104+
`register_spam_checker_callbacks` method.
100105

101106
The available spam checker callbacks are:
102107

@@ -115,7 +120,7 @@ async def user_may_invite(inviter: str, invitee: str, room_id: str) -> bool
115120

116121
Called when processing an invitation. The module must return a `bool` indicating whether
117122
the inviter can invite the invitee to the given room. Both inviter and invitee are
118-
represented by their Matrix user ID (i.e. `@alice:example.com`).
123+
represented by their Matrix user ID (e.g. `@alice:example.com`).
119124

120125
```python
121126
async def user_may_create_room(user: str) -> bool
@@ -188,6 +193,36 @@ async def check_media_file_for_spam(
188193
Called when storing a local or remote file. The module must return a boolean indicating
189194
whether the given file can be stored in the homeserver's media store.
190195

196+
#### Account validity callbacks
197+
198+
Account validity callbacks allow module developers to add extra steps to verify the
199+
validity on an account, i.e. see if a user can be granted access to their account on the
200+
Synapse instance. Account validity callbacks can be registered using the module API's
201+
`register_account_validity_callbacks` method.
202+
203+
The available account validity callbacks are:
204+
205+
```python
206+
async def is_user_expired(user: str) -> Optional[bool]
207+
```
208+
209+
Called when processing any authenticated request (except for logout requests). The module
210+
can return a `bool` to indicate whether the user has expired and should be locked out of
211+
their account, or `None` if the module wasn't able to figure it out. The user is
212+
represented by their Matrix user ID (e.g. `@alice:example.com`).
213+
214+
If the module returns `True`, the current request will be denied with the error code
215+
`ORG_MATRIX_EXPIRED_ACCOUNT` and the HTTP status code 403. Note that this doesn't
216+
invalidate the user's access token.
217+
218+
```python
219+
async def on_user_registration(user: str) -> None
220+
```
221+
222+
Called after successfully registering a user, in case the module needs to perform extra
223+
operations to keep track of them. (e.g. add them to a database table). The user is
224+
represented by their Matrix user ID.
225+
191226
### Porting an existing module that uses the old interface
192227

193228
In order to port a module that uses Synapse's old module interface, its author needs to:

docs/sample_config.yaml

Lines changed: 0 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,91 +1310,6 @@ account_threepid_delegates:
13101310
#auto_join_rooms_for_guests: false
13111311

13121312

1313-
## Account Validity ##
1314-
1315-
# Optional account validity configuration. This allows for accounts to be denied
1316-
# any request after a given period.
1317-
#
1318-
# Once this feature is enabled, Synapse will look for registered users without an
1319-
# expiration date at startup and will add one to every account it found using the
1320-
# current settings at that time.
1321-
# This means that, if a validity period is set, and Synapse is restarted (it will
1322-
# then derive an expiration date from the current validity period), and some time
1323-
# after that the validity period changes and Synapse is restarted, the users'
1324-
# expiration dates won't be updated unless their account is manually renewed. This
1325-
# date will be randomly selected within a range [now + period - d ; now + period],
1326-
# where d is equal to 10% of the validity period.
1327-
#
1328-
account_validity:
1329-
# The account validity feature is disabled by default. Uncomment the
1330-
# following line to enable it.
1331-
#
1332-
#enabled: true
1333-
1334-
# The period after which an account is valid after its registration. When
1335-
# renewing the account, its validity period will be extended by this amount
1336-
# of time. This parameter is required when using the account validity
1337-
# feature.
1338-
#
1339-
#period: 6w
1340-
1341-
# The amount of time before an account's expiry date at which Synapse will
1342-
# send an email to the account's email address with a renewal link. By
1343-
# default, no such emails are sent.
1344-
#
1345-
# If you enable this setting, you will also need to fill out the 'email' and
1346-
# 'public_baseurl' configuration sections.
1347-
#
1348-
#renew_at: 1w
1349-
1350-
# The subject of the email sent out with the renewal link. '%(app)s' can be
1351-
# used as a placeholder for the 'app_name' parameter from the 'email'
1352-
# section.
1353-
#
1354-
# Note that the placeholder must be written '%(app)s', including the
1355-
# trailing 's'.
1356-
#
1357-
# If this is not set, a default value is used.
1358-
#
1359-
#renew_email_subject: "Renew your %(app)s account"
1360-
1361-
# Directory in which Synapse will try to find templates for the HTML files to
1362-
# serve to the user when trying to renew an account. If not set, default
1363-
# templates from within the Synapse package will be used.
1364-
#
1365-
# The currently available templates are:
1366-
#
1367-
# * account_renewed.html: Displayed to the user after they have successfully
1368-
# renewed their account.
1369-
#
1370-
# * account_previously_renewed.html: Displayed to the user if they attempt to
1371-
# renew their account with a token that is valid, but that has already
1372-
# been used. In this case the account is not renewed again.
1373-
#
1374-
# * invalid_token.html: Displayed to the user when they try to renew an account
1375-
# with an unknown or invalid renewal token.
1376-
#
1377-
# See https://github.com/matrix-org/synapse/tree/master/synapse/res/templates for
1378-
# default template contents.
1379-
#
1380-
# The file name of some of these templates can be configured below for legacy
1381-
# reasons.
1382-
#
1383-
#template_dir: "res/templates"
1384-
1385-
# A custom file name for the 'account_renewed.html' template.
1386-
#
1387-
# If not set, the file is assumed to be named "account_renewed.html".
1388-
#
1389-
#account_renewed_html_path: "account_renewed.html"
1390-
1391-
# A custom file name for the 'invalid_token.html' template.
1392-
#
1393-
# If not set, the file is assumed to be named "invalid_token.html".
1394-
#
1395-
#invalid_token_html_path: "invalid_token.html"
1396-
1397-
13981313
## Metrics ###
13991314

14001315
# Enable collection and rendering of performance metrics

synapse/api/auth.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,14 @@ def __init__(self, hs: "HomeServer"):
6262
self.clock = hs.get_clock()
6363
self.store = hs.get_datastore()
6464
self.state = hs.get_state_handler()
65+
self._account_validity_handler = hs.get_account_validity_handler()
6566

6667
self.token_cache: LruCache[str, Tuple[str, bool]] = LruCache(
6768
10000, "token_cache"
6869
)
6970

7071
self._auth_blocking = AuthBlocking(self.hs)
7172

72-
self._account_validity_enabled = (
73-
hs.config.account_validity.account_validity_enabled
74-
)
7573
self._track_appservice_user_ips = hs.config.track_appservice_user_ips
7674
self._macaroon_secret_key = hs.config.macaroon_secret_key
7775
self._force_tracing_for_users = hs.config.tracing.force_tracing_for_users
@@ -187,12 +185,17 @@ async def get_user_by_req(
187185
shadow_banned = user_info.shadow_banned
188186

189187
# Deny the request if the user account has expired.
190-
if self._account_validity_enabled and not allow_expired:
191-
if await self.store.is_account_expired(
192-
user_info.user_id, self.clock.time_msec()
188+
if not allow_expired:
189+
if await self._account_validity_handler.is_user_expired(
190+
user_info.user_id
193191
):
192+
# Raise the error if either an account validity module has determined
193+
# the account has expired, or the legacy account validity
194+
# implementation is enabled and determined the account has expired
194195
raise AuthError(
195-
403, "User account has expired", errcode=Codes.EXPIRED_ACCOUNT
196+
403,
197+
"User account has expired",
198+
errcode=Codes.EXPIRED_ACCOUNT,
196199
)
197200

198201
device_id = user_info.device_id

synapse/config/account_validity.py

Lines changed: 15 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,21 @@ class AccountValidityConfig(Config):
1818
section = "account_validity"
1919

2020
def read_config(self, config, **kwargs):
21+
"""Parses the old account validity config. The config format looks like this:
22+
23+
account_validity:
24+
enabled: true
25+
period: 6w
26+
renew_at: 1w
27+
renew_email_subject: "Renew your %(app)s account"
28+
template_dir: "res/templates"
29+
account_renewed_html_path: "account_renewed.html"
30+
invalid_token_html_path: "invalid_token.html"
31+
32+
We expect admins to use modules for this feature (which is why it doesn't appear
33+
in the sample config file), but we want to keep support for it around for a bit
34+
for backwards compatibility.
35+
"""
2136
account_validity_config = config.get("account_validity") or {}
2237
self.account_validity_enabled = account_validity_config.get("enabled", False)
2338
self.account_validity_renew_by_email_enabled = (
@@ -75,90 +90,3 @@ def read_config(self, config, **kwargs):
7590
],
7691
account_validity_template_dir,
7792
)
78-
79-
def generate_config_section(self, **kwargs):
80-
return """\
81-
## Account Validity ##
82-
83-
# Optional account validity configuration. This allows for accounts to be denied
84-
# any request after a given period.
85-
#
86-
# Once this feature is enabled, Synapse will look for registered users without an
87-
# expiration date at startup and will add one to every account it found using the
88-
# current settings at that time.
89-
# This means that, if a validity period is set, and Synapse is restarted (it will
90-
# then derive an expiration date from the current validity period), and some time
91-
# after that the validity period changes and Synapse is restarted, the users'
92-
# expiration dates won't be updated unless their account is manually renewed. This
93-
# date will be randomly selected within a range [now + period - d ; now + period],
94-
# where d is equal to 10% of the validity period.
95-
#
96-
account_validity:
97-
# The account validity feature is disabled by default. Uncomment the
98-
# following line to enable it.
99-
#
100-
#enabled: true
101-
102-
# The period after which an account is valid after its registration. When
103-
# renewing the account, its validity period will be extended by this amount
104-
# of time. This parameter is required when using the account validity
105-
# feature.
106-
#
107-
#period: 6w
108-
109-
# The amount of time before an account's expiry date at which Synapse will
110-
# send an email to the account's email address with a renewal link. By
111-
# default, no such emails are sent.
112-
#
113-
# If you enable this setting, you will also need to fill out the 'email' and
114-
# 'public_baseurl' configuration sections.
115-
#
116-
#renew_at: 1w
117-
118-
# The subject of the email sent out with the renewal link. '%(app)s' can be
119-
# used as a placeholder for the 'app_name' parameter from the 'email'
120-
# section.
121-
#
122-
# Note that the placeholder must be written '%(app)s', including the
123-
# trailing 's'.
124-
#
125-
# If this is not set, a default value is used.
126-
#
127-
#renew_email_subject: "Renew your %(app)s account"
128-
129-
# Directory in which Synapse will try to find templates for the HTML files to
130-
# serve to the user when trying to renew an account. If not set, default
131-
# templates from within the Synapse package will be used.
132-
#
133-
# The currently available templates are:
134-
#
135-
# * account_renewed.html: Displayed to the user after they have successfully
136-
# renewed their account.
137-
#
138-
# * account_previously_renewed.html: Displayed to the user if they attempt to
139-
# renew their account with a token that is valid, but that has already
140-
# been used. In this case the account is not renewed again.
141-
#
142-
# * invalid_token.html: Displayed to the user when they try to renew an account
143-
# with an unknown or invalid renewal token.
144-
#
145-
# See https://github.com/matrix-org/synapse/tree/master/synapse/res/templates for
146-
# default template contents.
147-
#
148-
# The file name of some of these templates can be configured below for legacy
149-
# reasons.
150-
#
151-
#template_dir: "res/templates"
152-
153-
# A custom file name for the 'account_renewed.html' template.
154-
#
155-
# If not set, the file is assumed to be named "account_renewed.html".
156-
#
157-
#account_renewed_html_path: "account_renewed.html"
158-
159-
# A custom file name for the 'invalid_token.html' template.
160-
#
161-
# If not set, the file is assumed to be named "invalid_token.html".
162-
#
163-
#invalid_token_html_path: "invalid_token.html"
164-
"""

0 commit comments

Comments
 (0)