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

Consolidate logic to check for deactivated users. #15634

Merged
merged 4 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
3 changes: 2 additions & 1 deletion synapse/appservice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def __init__(
url.rstrip("/") if isinstance(url, str) else None
) # url must not end with a slash
self.hs_token = hs_token
# The full Matrix ID for this application service's sender.
self.sender = sender
self.namespaces = self._check_namespaces(namespaces)
self.id = id
Expand Down Expand Up @@ -212,7 +213,7 @@ def is_interested_in_user(
True if the application service is interested in the user, False if not.
"""
return (
# User is the appservice's sender_localpart user
# User is the appservice's configured sender_localpart user
user_id == self.sender
# User is in the appservice's user namespace
or self.is_user_in_namespace(user_id)
Expand Down
14 changes: 5 additions & 9 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
NotFoundError,
StoreError,
SynapseError,
UserDeactivatedError,
)
from synapse.api.ratelimiting import Ratelimiter
from synapse.handlers.ui_auth import (
Expand Down Expand Up @@ -1419,12 +1418,6 @@ async def _check_local_password(self, user_id: str, password: str) -> Optional[s
return None
(user_id, password_hash) = lookupres

# If the password hash is None, the account has likely been deactivated
if not password_hash:
deactivated = await self.store.get_user_deactivated_status(user_id)
if deactivated:
raise UserDeactivatedError("This account has been deactivated")

result = await self.validate_hash(password, password_hash)
if not result:
logger.warning("Failed password login for user %s", user_id)
Expand Down Expand Up @@ -1749,8 +1742,11 @@ async def complete_sso_login(
registered.
auth_provider_session_id: The session ID from the SSO IdP received during login.
"""
# If the account has been deactivated, do not proceed with the login
# flow.
# If the account has been deactivated, do not proceed with the login.
#
# This gets checked again when the token is submitted but this lets us
# provide an HTML error page to the user (instead of issuing a token and
# having it error later).
deactivated = await self.store.get_user_deactivated_status(registered_user_id)
if deactivated:
respond_with_html(request, 403, self._sso_account_deactivated_template)
Expand Down
19 changes: 3 additions & 16 deletions synapse/handlers/jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from authlib.jose import JsonWebToken, JWTClaims
from authlib.jose.errors import BadSignatureError, InvalidClaimError, JoseError

from synapse.api.errors import Codes, LoginError, StoreError, UserDeactivatedError
from synapse.api.errors import Codes, LoginError
from synapse.types import JsonDict, UserID

if TYPE_CHECKING:
Expand All @@ -26,15 +26,14 @@
class JwtHandler:
def __init__(self, hs: "HomeServer"):
self.hs = hs
self._main_store = hs.get_datastores().main

self.jwt_secret = hs.config.jwt.jwt_secret
self.jwt_subject_claim = hs.config.jwt.jwt_subject_claim
self.jwt_algorithm = hs.config.jwt.jwt_algorithm
self.jwt_issuer = hs.config.jwt.jwt_issuer
self.jwt_audiences = hs.config.jwt.jwt_audiences

async def validate_login(self, login_submission: JsonDict) -> str:
def validate_login(self, login_submission: JsonDict) -> str:
"""
Authenticates the user for the /login API

Expand Down Expand Up @@ -103,16 +102,4 @@ async def validate_login(self, login_submission: JsonDict) -> str:
if user is None:
raise LoginError(403, "Invalid JWT", errcode=Codes.FORBIDDEN)

user_id = UserID(user, self.hs.hostname).to_string()

# If the account has been deactivated, do not proceed with the login
# flow.
try:
deactivated = await self._main_store.get_user_deactivated_status(user_id)
except StoreError:
# JWT lazily creates users, so they may not exist in the database yet.
deactivated = False
if deactivated:
raise UserDeactivatedError("This account has been deactivated")
clokep marked this conversation as resolved.
Show resolved Hide resolved

return user_id
return UserID(user, self.hs.hostname).to_string()
23 changes: 20 additions & 3 deletions synapse/rest/client/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
LoginError,
NotApprovedError,
SynapseError,
UserDeactivatedError,
)
from synapse.api.ratelimiting import Ratelimiter
from synapse.api.urls import CLIENT_API_PREFIX
Expand Down Expand Up @@ -84,6 +85,7 @@ class LoginRestServlet(RestServlet):
def __init__(self, hs: "HomeServer"):
super().__init__()
self.hs = hs
self._main_store = hs.get_datastores().main

# JWT configuration variables.
self.jwt_enabled = hs.config.jwt.jwt_enabled
Expand Down Expand Up @@ -112,13 +114,13 @@ def __init__(self, hs: "HomeServer"):

self._well_known_builder = WellKnownBuilder(hs)
self._address_ratelimiter = Ratelimiter(
store=hs.get_datastores().main,
store=self._main_store,
clock=hs.get_clock(),
rate_hz=self.hs.config.ratelimiting.rc_login_address.per_second,
burst_count=self.hs.config.ratelimiting.rc_login_address.burst_count,
)
self._account_ratelimiter = Ratelimiter(
store=hs.get_datastores().main,
store=self._main_store,
clock=hs.get_clock(),
rate_hz=self.hs.config.ratelimiting.rc_login_account.per_second,
burst_count=self.hs.config.ratelimiting.rc_login_account.burst_count,
Expand Down Expand Up @@ -280,6 +282,9 @@ async def _do_appservice_login(
login_submission,
ratelimit=appservice.is_rate_limited(),
should_issue_refresh_token=should_issue_refresh_token,
# The user represented by an appservice's configured sender_localpart
# is not actually created in Synapse.
should_check_deactivated=qualified_user_id != appservice.sender,
DMRobertson marked this conversation as resolved.
Show resolved Hide resolved
)

async def _do_other_login(
Expand Down Expand Up @@ -326,6 +331,7 @@ async def _complete_login(
auth_provider_id: Optional[str] = None,
should_issue_refresh_token: bool = False,
auth_provider_session_id: Optional[str] = None,
should_check_deactivated: bool = True,
) -> LoginResponse:
"""Called when we've successfully authed the user and now need to
actually login them in (e.g. create devices). This gets called on
Expand All @@ -345,6 +351,11 @@ async def _complete_login(
should_issue_refresh_token: True if this login should issue
a refresh token alongside the access token.
auth_provider_session_id: The session ID got during login from the SSO IdP.
should_check_deactivated: True if the user should be checked for
deactivation status before logging in.

This exists purely for appservice's configured sender_localpart
which deosn't have an associated user in the database.
clokep marked this conversation as resolved.
Show resolved Hide resolved

Returns:
Dictionary of account information after successful login.
Expand All @@ -364,6 +375,12 @@ async def _complete_login(
)
user_id = canonical_uid

# If the account has been deactivated, do not proceed with the login.
if should_check_deactivated:
deactivated = await self._main_store.get_user_deactivated_status(user_id)
if deactivated:
raise UserDeactivatedError("This account has been deactivated")

device_id = login_submission.get("device_id")

# If device_id is present, check that device_id is not longer than a reasonable 512 characters
Expand Down Expand Up @@ -458,7 +475,7 @@ async def _do_jwt_login(
Returns:
The body of the JSON response.
"""
user_id = await self.hs.get_jwt_handler().validate_login(login_submission)
user_id = self.hs.get_jwt_handler().validate_login(login_submission)
return await self._complete_login(
user_id,
login_submission,
Expand Down