2020-05-06 10:54:58 -04:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
import logging
|
2020-11-17 05:51:25 -05:00
|
|
|
from typing import Optional
|
2020-05-06 10:54:58 -04:00
|
|
|
|
|
|
|
from synapse.api.constants import LimitBlockingTypes, UserTypes
|
|
|
|
from synapse.api.errors import Codes, ResourceLimitError
|
|
|
|
from synapse.config.server import is_threepid_reserved
|
2020-11-17 05:51:25 -05:00
|
|
|
from synapse.types import Requester
|
2020-05-06 10:54:58 -04:00
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2020-09-04 06:54:56 -04:00
|
|
|
class AuthBlocking:
|
2020-05-06 10:54:58 -04:00
|
|
|
def __init__(self, hs):
|
|
|
|
self.store = hs.get_datastore()
|
|
|
|
|
|
|
|
self._server_notices_mxid = hs.config.server_notices_mxid
|
|
|
|
self._hs_disabled = hs.config.hs_disabled
|
|
|
|
self._hs_disabled_message = hs.config.hs_disabled_message
|
|
|
|
self._admin_contact = hs.config.admin_contact
|
|
|
|
self._max_mau_value = hs.config.max_mau_value
|
|
|
|
self._limit_usage_by_mau = hs.config.limit_usage_by_mau
|
|
|
|
self._mau_limits_reserved_threepids = hs.config.mau_limits_reserved_threepids
|
2020-11-17 05:51:25 -05:00
|
|
|
self._server_name = hs.hostname
|
2020-05-06 10:54:58 -04:00
|
|
|
|
2020-11-17 05:51:25 -05:00
|
|
|
async def check_auth_blocking(
|
|
|
|
self,
|
|
|
|
user_id: Optional[str] = None,
|
|
|
|
threepid: Optional[dict] = None,
|
|
|
|
user_type: Optional[str] = None,
|
|
|
|
requester: Optional[Requester] = None,
|
|
|
|
):
|
2020-05-06 10:54:58 -04:00
|
|
|
"""Checks if the user should be rejected for some external reason,
|
|
|
|
such as monthly active user limiting or global disable flag
|
|
|
|
|
|
|
|
Args:
|
2020-11-17 05:51:25 -05:00
|
|
|
user_id: If present, checks for presence against existing
|
2020-05-06 10:54:58 -04:00
|
|
|
MAU cohort
|
|
|
|
|
2020-11-17 05:51:25 -05:00
|
|
|
threepid: If present, checks for presence against configured
|
2020-05-06 10:54:58 -04:00
|
|
|
reserved threepid. Used in cases where the user is trying register
|
|
|
|
with a MAU blocked server, normally they would be rejected but their
|
|
|
|
threepid is on the reserved list. user_id and
|
|
|
|
threepid should never be set at the same time.
|
|
|
|
|
2020-11-17 05:51:25 -05:00
|
|
|
user_type: If present, is used to decide whether to check against
|
2020-05-06 10:54:58 -04:00
|
|
|
certain blocking reasons like MAU.
|
2020-11-17 05:51:25 -05:00
|
|
|
|
|
|
|
requester: If present, and the authenticated entity is a user, checks for
|
|
|
|
presence against existing MAU cohort. Passing in both a `user_id` and
|
|
|
|
`requester` is an error.
|
2020-05-06 10:54:58 -04:00
|
|
|
"""
|
2020-11-17 05:51:25 -05:00
|
|
|
if requester and user_id:
|
|
|
|
raise Exception(
|
|
|
|
"Passed in both 'user_id' and 'requester' to 'check_auth_blocking'"
|
|
|
|
)
|
|
|
|
|
|
|
|
if requester:
|
|
|
|
if requester.authenticated_entity.startswith("@"):
|
|
|
|
user_id = requester.authenticated_entity
|
|
|
|
elif requester.authenticated_entity == self._server_name:
|
|
|
|
# We never block the server from doing actions on behalf of
|
|
|
|
# users.
|
|
|
|
return
|
2020-05-06 10:54:58 -04:00
|
|
|
|
|
|
|
# Never fail an auth check for the server notices users or support user
|
|
|
|
# This can be a problem where event creation is prohibited due to blocking
|
|
|
|
if user_id is not None:
|
|
|
|
if user_id == self._server_notices_mxid:
|
|
|
|
return
|
2020-08-06 08:30:06 -04:00
|
|
|
if await self.store.is_support_user(user_id):
|
2020-05-06 10:54:58 -04:00
|
|
|
return
|
|
|
|
|
|
|
|
if self._hs_disabled:
|
|
|
|
raise ResourceLimitError(
|
|
|
|
403,
|
|
|
|
self._hs_disabled_message,
|
|
|
|
errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
|
|
|
|
admin_contact=self._admin_contact,
|
|
|
|
limit_type=LimitBlockingTypes.HS_DISABLED,
|
|
|
|
)
|
|
|
|
if self._limit_usage_by_mau is True:
|
|
|
|
assert not (user_id and threepid)
|
|
|
|
|
|
|
|
# If the user is already part of the MAU cohort or a trial user
|
|
|
|
if user_id:
|
2020-08-06 08:30:06 -04:00
|
|
|
timestamp = await self.store.user_last_seen_monthly_active(user_id)
|
2020-05-06 10:54:58 -04:00
|
|
|
if timestamp:
|
|
|
|
return
|
|
|
|
|
2020-08-06 08:30:06 -04:00
|
|
|
is_trial = await self.store.is_trial_user(user_id)
|
2020-05-06 10:54:58 -04:00
|
|
|
if is_trial:
|
|
|
|
return
|
|
|
|
elif threepid:
|
|
|
|
# If the user does not exist yet, but is signing up with a
|
|
|
|
# reserved threepid then pass auth check
|
|
|
|
if is_threepid_reserved(self._mau_limits_reserved_threepids, threepid):
|
|
|
|
return
|
|
|
|
elif user_type == UserTypes.SUPPORT:
|
|
|
|
# If the user does not exist yet and is of type "support",
|
|
|
|
# allow registration. Support users are excluded from MAU checks.
|
|
|
|
return
|
|
|
|
# Else if there is no room in the MAU bucket, bail
|
2020-08-06 08:30:06 -04:00
|
|
|
current_mau = await self.store.get_monthly_active_count()
|
2020-05-06 10:54:58 -04:00
|
|
|
if current_mau >= self._max_mau_value:
|
|
|
|
raise ResourceLimitError(
|
|
|
|
403,
|
|
|
|
"Monthly Active User Limit Exceeded",
|
|
|
|
admin_contact=self._admin_contact,
|
|
|
|
errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
|
|
|
|
limit_type=LimitBlockingTypes.MONTHLY_ACTIVE_USER,
|
|
|
|
)
|