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

Add a module type for account validity #9884

Merged
merged 55 commits into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
2d71db2
Add additional required capabilities to the module API
babolivier Apr 26, 2021
163ca38
Change the account validity configuration
babolivier Apr 26, 2021
2dd3a35
Add an API for account validity modules
babolivier Apr 26, 2021
850b303
Plug the new account validity APIs onto the right places
babolivier Apr 26, 2021
f8c8ca8
Changelog
babolivier Apr 26, 2021
f492ef3
Allow modules to query whether the user is a server admin
babolivier Apr 26, 2021
5596cfe
The module API already exposes run_in_background which makes backgrou…
babolivier Apr 26, 2021
456a06c
Fix user authentication in the module API
babolivier Apr 27, 2021
94706a9
Add a hook for the legacy admin API
babolivier Apr 27, 2021
576b9f3
Fix comment with right URL path
babolivier Apr 27, 2021
907b655
Add a deprecation notice to the upgrade notes
babolivier Apr 27, 2021
9a9e83c
Lint
babolivier Apr 27, 2021
588fa41
Lint
babolivier Apr 27, 2021
13a72ee
Mention the module in the config warning (+typo)
babolivier Apr 27, 2021
fe2e8ca
Incorporate part of the review
babolivier May 7, 2021
2147f06
Split multiplart email sending into a dedicated handler
babolivier May 13, 2021
1df0c8c
Changelog
babolivier May 13, 2021
a26185a
Lint
babolivier May 13, 2021
7b59ea5
Merge branch 'babolivier/send_mail' into babolivier/account_validity_…
babolivier May 13, 2021
76aed9e
Incorporate review
babolivier May 13, 2021
efc82b7
Fix typo in changelog file
babolivier May 13, 2021
1df5d74
Incorporate review
babolivier May 13, 2021
0d7cb16
Typo
babolivier May 13, 2021
a36be02
Merge branch 'babolivier/send_mail' into babolivier/account_validity_…
babolivier May 13, 2021
7b553e6
Expose more things needed by the email account validity module on the…
babolivier May 14, 2021
d0a1cd5
Update docs + lint
babolivier May 19, 2021
602d2ce
Get the email app name from the email config
babolivier May 20, 2021
df82691
Merge branch 'develop' into babolivier/account_validity_plugin
babolivier May 20, 2021
14885d3
Fix types
babolivier May 20, 2021
3108884
Fix imports
babolivier May 20, 2021
d0d8a5b
Merge branch 'develop' into babolivier/account_validity_plugin
babolivier Jun 28, 2021
c52921e
Move the account validity module interface to the new system
babolivier Jul 1, 2021
c964099
Revert changes account validity config since the module config lives …
babolivier Jul 2, 2021
a6346e9
Remove deprecation warning
babolivier Jul 2, 2021
51b5233
Sample config
babolivier Jul 2, 2021
c2185cf
Incorporate review comments
babolivier Jul 2, 2021
a0ca661
Fix type of on_user_registration callbacks
babolivier Jul 2, 2021
94ca9a7
Fix types
babolivier Jul 2, 2021
34dc6ea
Restore the possibility for is_user_expired to return None
babolivier Jul 2, 2021
49c4582
Document the account validity callbacks
babolivier Jul 2, 2021
005725d
Fix tests
babolivier Jul 2, 2021
f8754fe
Document why we need legacy hooks
babolivier Jul 5, 2021
58bb7b1
Apply suggestions from code review
babolivier Jul 7, 2021
4fb4b49
Incorporate part of the review
babolivier Jul 7, 2021
b45b45d
Use the account validity handler in the pusher pool
babolivier Jul 7, 2021
972091d
Lint
babolivier Jul 7, 2021
07aa404
Fix changelog
babolivier Jul 7, 2021
ba4e069
Improve docs
babolivier Jul 8, 2021
d9ee0f9
Line break
babolivier Jul 9, 2021
c2b6689
Incorporate review
babolivier Jul 14, 2021
a4adb3d
Lint
babolivier Jul 14, 2021
c3debd5
Apply suggestions from code review
babolivier Jul 16, 2021
e87c3cb
Incorporate review
babolivier Jul 16, 2021
01b6bf3
Merge branch 'babolivier/account_validity_plugin' of github.com:matri…
babolivier Jul 16, 2021
1a8af46
Lint
babolivier Jul 16, 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/9884.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a module type for the account validity feature.
45 changes: 40 additions & 5 deletions docs/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,17 @@ are split in categories. A single module may implement callbacks from multiple c
and is under no obligation to implement all callbacks from the categories it registers
callbacks for.

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

#### Spam checker callbacks

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

The available spam checker callbacks are:

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

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

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

#### Account validity callbacks

Account validity callbacks allow module developers to add extra steps to verify the
validity on an account, i.e. see if a user can be granted access to their account on the
Synapse instance. Account validity callbacks can be registered using the module API's
`register_account_validity_callbacks` method.

The available account validity callbacks are:

```python
async def is_user_expired(user: str) -> Optional[bool]
```

Called when processing any authenticated request (except for logout requests). The module
can return a `bool` to indicate whether the user has expired and should be locked out of
their account, or `None` if the module wasn't able to figure it out. The user is
represented by their Matrix user ID (e.g. `@alice:example.com`).

If the module returns `True`, the current request will be denied with the error code
`ORG_MATRIX_EXPIRED_ACCOUNT` and the HTTP status code 403. Note that this doesn't
invalidate the user's access token.

```python
async def on_user_registration(user: str)
babolivier marked this conversation as resolved.
Show resolved Hide resolved
```

Called after successfully registering a user, in case the module needs to perform extra
operations to keep track of them. (e.g. add them to a database table). The user is
represented by their Matrix user ID.

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

In order to port a module that uses Synapse's old module interface, its author needs to:
Expand Down
85 changes: 0 additions & 85 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1301,91 +1301,6 @@ account_threepid_delegates:
#auto_join_rooms_for_guests: false


## Account Validity ##

# Optional account validity configuration. This allows for accounts to be denied
# any request after a given period.
#
# Once this feature is enabled, Synapse will look for registered users without an
# expiration date at startup and will add one to every account it found using the
# current settings at that time.
# This means that, if a validity period is set, and Synapse is restarted (it will
# then derive an expiration date from the current validity period), and some time
# after that the validity period changes and Synapse is restarted, the users'
# expiration dates won't be updated unless their account is manually renewed. This
# date will be randomly selected within a range [now + period - d ; now + period],
# where d is equal to 10% of the validity period.
#
account_validity:
# The account validity feature is disabled by default. Uncomment the
# following line to enable it.
#
#enabled: true

# The period after which an account is valid after its registration. When
# renewing the account, its validity period will be extended by this amount
# of time. This parameter is required when using the account validity
# feature.
#
#period: 6w

# The amount of time before an account's expiry date at which Synapse will
# send an email to the account's email address with a renewal link. By
# default, no such emails are sent.
#
# If you enable this setting, you will also need to fill out the 'email' and
# 'public_baseurl' configuration sections.
#
#renew_at: 1w

# The subject of the email sent out with the renewal link. '%(app)s' can be
# used as a placeholder for the 'app_name' parameter from the 'email'
# section.
#
# Note that the placeholder must be written '%(app)s', including the
# trailing 's'.
#
# If this is not set, a default value is used.
#
#renew_email_subject: "Renew your %(app)s account"

# Directory in which Synapse will try to find templates for the HTML files to
# serve to the user when trying to renew an account. If not set, default
# templates from within the Synapse package will be used.
#
# The currently available templates are:
#
# * account_renewed.html: Displayed to the user after they have successfully
# renewed their account.
#
# * account_previously_renewed.html: Displayed to the user if they attempt to
# renew their account with a token that is valid, but that has already
# been used. In this case the account is not renewed again.
#
# * invalid_token.html: Displayed to the user when they try to renew an account
# with an unknown or invalid renewal token.
#
# See https://github.com/matrix-org/synapse/tree/master/synapse/res/templates for
# default template contents.
#
# The file name of some of these templates can be configured below for legacy
# reasons.
#
#template_dir: "res/templates"

# A custom file name for the 'account_renewed.html' template.
#
# If not set, the file is assumed to be named "account_renewed.html".
#
#account_renewed_html_path: "account_renewed.html"

# A custom file name for the 'invalid_token.html' template.
#
# If not set, the file is assumed to be named "invalid_token.html".
#
#invalid_token_html_path: "invalid_token.html"


## Metrics ###

# Enable collection and rendering of performance metrics
Expand Down
17 changes: 10 additions & 7 deletions synapse/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,14 @@ def __init__(self, hs: "HomeServer"):
self.clock = hs.get_clock()
self.store = hs.get_datastore()
self.state = hs.get_state_handler()
self._account_validity_handler = hs.get_account_validity_handler()
babolivier marked this conversation as resolved.
Show resolved Hide resolved

self.token_cache = LruCache(
10000, "token_cache"
) # type: LruCache[str, Tuple[str, bool]]

self._auth_blocking = AuthBlocking(self.hs)

self._account_validity_enabled = (
hs.config.account_validity.account_validity_enabled
)
self._track_appservice_user_ips = hs.config.track_appservice_user_ips
self._macaroon_secret_key = hs.config.macaroon_secret_key
self._force_tracing_for_users = hs.config.tracing.force_tracing_for_users
Expand Down Expand Up @@ -219,12 +217,17 @@ async def get_user_by_req(
shadow_banned = user_info.shadow_banned

# Deny the request if the user account has expired.
if self._account_validity_enabled and not allow_expired:
babolivier marked this conversation as resolved.
Show resolved Hide resolved
if await self.store.is_account_expired(
user_info.user_id, self.clock.time_msec()
if not allow_expired:
if await self._account_validity_handler.is_user_expired(
user_info.user_id
):
# Raise the error if either an account validity module has determined
# the account has expired, or the legacy account validity
# implementation is enabled and determined the account has expired
raise AuthError(
403, "User account has expired", errcode=Codes.EXPIRED_ACCOUNT
403,
"User account has expired",
errcode=Codes.EXPIRED_ACCOUNT,
)

device_id = user_info.device_id
Expand Down
102 changes: 15 additions & 87 deletions synapse/config/account_validity.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ class AccountValidityConfig(Config):
section = "account_validity"

def read_config(self, config, **kwargs):
"""Parses the old account validity config. The config format looks like this:

account_validity:
enabled: true
period: 6w
renew_at: 1w
renew_email_subject: "Renew your %(app)s account"
template_dir: "res/templates"
account_renewed_html_path: "account_renewed.html"
invalid_token_html_path: "invalid_token.html"

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.
"""
account_validity_config = config.get("account_validity") or {}
self.account_validity_enabled = account_validity_config.get("enabled", False)
self.account_validity_renew_by_email_enabled = (
Expand Down Expand Up @@ -75,90 +90,3 @@ def read_config(self, config, **kwargs):
],
account_validity_template_dir,
)

def generate_config_section(self, **kwargs):
babolivier marked this conversation as resolved.
Show resolved Hide resolved
return """\
## Account Validity ##

# Optional account validity configuration. This allows for accounts to be denied
# any request after a given period.
#
# Once this feature is enabled, Synapse will look for registered users without an
# expiration date at startup and will add one to every account it found using the
# current settings at that time.
# This means that, if a validity period is set, and Synapse is restarted (it will
# then derive an expiration date from the current validity period), and some time
# after that the validity period changes and Synapse is restarted, the users'
# expiration dates won't be updated unless their account is manually renewed. This
# date will be randomly selected within a range [now + period - d ; now + period],
# where d is equal to 10% of the validity period.
#
account_validity:
# The account validity feature is disabled by default. Uncomment the
# following line to enable it.
#
#enabled: true

# The period after which an account is valid after its registration. When
# renewing the account, its validity period will be extended by this amount
# of time. This parameter is required when using the account validity
# feature.
#
#period: 6w

# The amount of time before an account's expiry date at which Synapse will
# send an email to the account's email address with a renewal link. By
# default, no such emails are sent.
#
# If you enable this setting, you will also need to fill out the 'email' and
# 'public_baseurl' configuration sections.
#
#renew_at: 1w

# The subject of the email sent out with the renewal link. '%(app)s' can be
# used as a placeholder for the 'app_name' parameter from the 'email'
# section.
#
# Note that the placeholder must be written '%(app)s', including the
# trailing 's'.
#
# If this is not set, a default value is used.
#
#renew_email_subject: "Renew your %(app)s account"

# Directory in which Synapse will try to find templates for the HTML files to
# serve to the user when trying to renew an account. If not set, default
# templates from within the Synapse package will be used.
#
# The currently available templates are:
#
# * account_renewed.html: Displayed to the user after they have successfully
# renewed their account.
#
# * account_previously_renewed.html: Displayed to the user if they attempt to
# renew their account with a token that is valid, but that has already
# been used. In this case the account is not renewed again.
#
# * invalid_token.html: Displayed to the user when they try to renew an account
# with an unknown or invalid renewal token.
#
# See https://github.com/matrix-org/synapse/tree/master/synapse/res/templates for
# default template contents.
#
# The file name of some of these templates can be configured below for legacy
# reasons.
#
#template_dir: "res/templates"

# A custom file name for the 'account_renewed.html' template.
#
# If not set, the file is assumed to be named "account_renewed.html".
#
#account_renewed_html_path: "account_renewed.html"

# A custom file name for the 'invalid_token.html' template.
#
# If not set, the file is assumed to be named "invalid_token.html".
#
#invalid_token_html_path: "invalid_token.html"
"""
Loading