mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2025-05-02 16:16:04 -04:00
Add ability for access tokens to belong to one user but grant access to another user. (#8616)
We do it this way round so that only the "owner" can delete the access token (i.e. `/logout/all` by the "owner" also deletes that token, but `/logout/all` by the "target user" doesn't). A future PR will add an API for creating such a token. When the target user and authenticated entity are different the `Processed request` log line will be logged with a: `{@admin:server as @bob:server} ...`. I'm not convinced by that format (especially since it adds spaces in there, making it harder to use `cut -d ' '` to chop off the start of log lines). Suggestions welcome.
This commit is contained in:
parent
22eeb6bc54
commit
f21e24ffc2
22 changed files with 197 additions and 138 deletions
|
@ -18,6 +18,8 @@ import logging
|
|||
import re
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
|
||||
|
||||
import attr
|
||||
|
||||
from synapse.api.constants import UserTypes
|
||||
from synapse.api.errors import Codes, StoreError, SynapseError, ThreepidValidationError
|
||||
from synapse.metrics.background_process_metrics import wrap_as_background_process
|
||||
|
@ -38,6 +40,35 @@ THIRTY_MINUTES_IN_MS = 30 * 60 * 1000
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@attr.s(frozen=True, slots=True)
|
||||
class TokenLookupResult:
|
||||
"""Result of looking up an access token.
|
||||
|
||||
Attributes:
|
||||
user_id: The user that this token authenticates as
|
||||
is_guest
|
||||
shadow_banned
|
||||
token_id: The ID of the access token looked up
|
||||
device_id: The device associated with the token, if any.
|
||||
valid_until_ms: The timestamp the token expires, if any.
|
||||
token_owner: The "owner" of the token. This is either the same as the
|
||||
user, or a server admin who is logged in as the user.
|
||||
"""
|
||||
|
||||
user_id = attr.ib(type=str)
|
||||
is_guest = attr.ib(type=bool, default=False)
|
||||
shadow_banned = attr.ib(type=bool, default=False)
|
||||
token_id = attr.ib(type=Optional[int], default=None)
|
||||
device_id = attr.ib(type=Optional[str], default=None)
|
||||
valid_until_ms = attr.ib(type=Optional[int], default=None)
|
||||
token_owner = attr.ib(type=str)
|
||||
|
||||
# Make the token owner default to the user ID, which is the common case.
|
||||
@token_owner.default
|
||||
def _default_token_owner(self):
|
||||
return self.user_id
|
||||
|
||||
|
||||
class RegistrationWorkerStore(CacheInvalidationWorkerStore):
|
||||
def __init__(self, database: DatabasePool, db_conn: Connection, hs: "HomeServer"):
|
||||
super().__init__(database, db_conn, hs)
|
||||
|
@ -102,15 +133,13 @@ class RegistrationWorkerStore(CacheInvalidationWorkerStore):
|
|||
return is_trial
|
||||
|
||||
@cached()
|
||||
async def get_user_by_access_token(self, token: str) -> Optional[dict]:
|
||||
async def get_user_by_access_token(self, token: str) -> Optional[TokenLookupResult]:
|
||||
"""Get a user from the given access token.
|
||||
|
||||
Args:
|
||||
token: The access token of a user.
|
||||
Returns:
|
||||
None, if the token did not match, otherwise dict
|
||||
including the keys `name`, `is_guest`, `device_id`, `token_id`,
|
||||
`valid_until_ms`.
|
||||
None, if the token did not match, otherwise a `TokenLookupResult`
|
||||
"""
|
||||
return await self.db_pool.runInteraction(
|
||||
"get_user_by_access_token", self._query_for_auth, token
|
||||
|
@ -331,23 +360,24 @@ class RegistrationWorkerStore(CacheInvalidationWorkerStore):
|
|||
|
||||
await self.db_pool.runInteraction("set_server_admin", set_server_admin_txn)
|
||||
|
||||
def _query_for_auth(self, txn, token):
|
||||
def _query_for_auth(self, txn, token: str) -> Optional[TokenLookupResult]:
|
||||
sql = """
|
||||
SELECT users.name,
|
||||
SELECT users.name as user_id,
|
||||
users.is_guest,
|
||||
users.shadow_banned,
|
||||
access_tokens.id as token_id,
|
||||
access_tokens.device_id,
|
||||
access_tokens.valid_until_ms
|
||||
access_tokens.valid_until_ms,
|
||||
access_tokens.user_id as token_owner
|
||||
FROM users
|
||||
INNER JOIN access_tokens on users.name = access_tokens.user_id
|
||||
INNER JOIN access_tokens on users.name = COALESCE(puppets_user_id, access_tokens.user_id)
|
||||
WHERE token = ?
|
||||
"""
|
||||
|
||||
txn.execute(sql, (token,))
|
||||
rows = self.db_pool.cursor_to_dict(txn)
|
||||
if rows:
|
||||
return rows[0]
|
||||
return TokenLookupResult(**rows[0])
|
||||
|
||||
return None
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/* 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.
|
||||
*/
|
||||
|
||||
-- Whether the access token is an admin token for controlling another user.
|
||||
ALTER TABLE access_tokens ADD COLUMN puppets_user_id TEXT;
|
Loading…
Add table
Add a link
Reference in a new issue