mirror of
https://mau.dev/maunium/synapse.git
synced 2024-12-14 19:53:51 +01:00
SYN-48: Implement WHOIS rest servlet
This commit is contained in:
parent
c65306f877
commit
3ccb17ce59
9 changed files with 190 additions and 25 deletions
|
@ -220,7 +220,8 @@ class Auth(object):
|
|||
# Can optionally look elsewhere in the request (e.g. headers)
|
||||
try:
|
||||
access_token = request.args["access_token"][0]
|
||||
user = yield self.get_user_by_token(access_token)
|
||||
user_info = yield self.get_user_by_token(access_token)
|
||||
user = user_info["user"]
|
||||
|
||||
ip_addr = self.hs.get_ip_from_request(request)
|
||||
user_agent = request.requestHeaders.getRawHeaders(
|
||||
|
@ -229,10 +230,11 @@ class Auth(object):
|
|||
)[0]
|
||||
if user and access_token and ip_addr:
|
||||
self.store.insert_client_ip(
|
||||
user,
|
||||
access_token,
|
||||
ip_addr,
|
||||
user_agent
|
||||
user=user,
|
||||
access_token=access_token,
|
||||
device_id=user_info["device_id"],
|
||||
ip=ip_addr,
|
||||
user_agent=user_agent
|
||||
)
|
||||
|
||||
defer.returnValue(user)
|
||||
|
@ -246,15 +248,23 @@ class Auth(object):
|
|||
Args:
|
||||
token (str)- The access token to get the user by.
|
||||
Returns:
|
||||
UserID : User ID object of the user who has that access token.
|
||||
dict : dict that includes the user, device_id, and whether the
|
||||
user is a server admin.
|
||||
Raises:
|
||||
AuthError if no user by that token exists or the token is invalid.
|
||||
"""
|
||||
try:
|
||||
user_id = yield self.store.get_user_by_token(token=token)
|
||||
if not user_id:
|
||||
ret = yield self.store.get_user_by_token(token=token)
|
||||
if not ret:
|
||||
raise StoreError()
|
||||
defer.returnValue(self.hs.parse_userid(user_id))
|
||||
|
||||
user_info = {
|
||||
"admin": bool(ret.get("admin", False)),
|
||||
"device_id": ret.get("device_id"),
|
||||
"user": self.hs.parse_userid(ret.get("name")),
|
||||
}
|
||||
|
||||
defer.returnValue(user_info)
|
||||
except StoreError:
|
||||
raise AuthError(403, "Unrecognised access token.",
|
||||
errcode=Codes.UNKNOWN_TOKEN)
|
||||
|
|
|
@ -25,6 +25,7 @@ from .profile import ProfileHandler
|
|||
from .presence import PresenceHandler
|
||||
from .directory import DirectoryHandler
|
||||
from .typing import TypingNotificationHandler
|
||||
from .admin import AdminHandler
|
||||
|
||||
|
||||
class Handlers(object):
|
||||
|
@ -49,3 +50,4 @@ class Handlers(object):
|
|||
self.login_handler = LoginHandler(hs)
|
||||
self.directory_handler = DirectoryHandler(hs)
|
||||
self.typing_notification_handler = TypingNotificationHandler(hs)
|
||||
self.admin_handler = AdminHandler(hs)
|
||||
|
|
62
synapse/handlers/admin.py
Normal file
62
synapse/handlers/admin.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2014 OpenMarket Ltd
|
||||
#
|
||||
# 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.
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from ._base import BaseHandler
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AdminHandler(BaseHandler):
|
||||
|
||||
def __init__(self, hs):
|
||||
super(AdminHandler, self).__init__(hs)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def get_whois(self, user):
|
||||
res = yield self.store.get_user_ip_and_agents(user)
|
||||
|
||||
d = {}
|
||||
for r in res:
|
||||
device = d.setdefault(r["device_id"], {})
|
||||
session = device.setdefault(r["access_token"], [])
|
||||
session.append({
|
||||
"ip": r["ip"],
|
||||
"user_agent": r["user_agent"],
|
||||
"last_seen": r["last_seen"],
|
||||
})
|
||||
|
||||
ret = {
|
||||
"user_id": user.to_string(),
|
||||
"devices": [
|
||||
{
|
||||
"device_id": k,
|
||||
"sessions": [
|
||||
{
|
||||
# "access_token": x, TODO (erikj)
|
||||
"connections": y,
|
||||
}
|
||||
for x, y in v.items()
|
||||
]
|
||||
}
|
||||
for k, v in d.items()
|
||||
],
|
||||
}
|
||||
|
||||
defer.returnValue(ret)
|
|
@ -15,7 +15,8 @@
|
|||
|
||||
|
||||
from . import (
|
||||
room, events, register, login, profile, presence, initial_sync, directory, voip
|
||||
room, events, register, login, profile, presence, initial_sync, directory,
|
||||
voip, admin,
|
||||
)
|
||||
|
||||
|
||||
|
@ -43,3 +44,4 @@ class RestServletFactory(object):
|
|||
initial_sync.register_servlets(hs, client_resource)
|
||||
directory.register_servlets(hs, client_resource)
|
||||
voip.register_servlets(hs, client_resource)
|
||||
admin.register_servlets(hs, client_resource)
|
||||
|
|
47
synapse/rest/admin.py
Normal file
47
synapse/rest/admin.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2014 OpenMarket Ltd
|
||||
#
|
||||
# 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.
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.api.errors import AuthError, SynapseError
|
||||
from base import RestServlet, client_path_pattern
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WhoisRestServlet(RestServlet):
|
||||
PATTERN = client_path_pattern("/admin/whois/(?P<user_id>[^/]*)")
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def on_GET(self, request, user_id):
|
||||
target_user = self.hs.parse_userid(user_id)
|
||||
auth_user = yield self.auth.get_user_by_req(request)
|
||||
is_admin = yield self.auth.is_server_admin(auth_user)
|
||||
|
||||
if not is_admin and target_user != auth_user:
|
||||
raise AuthError(403, "You are not a server admin")
|
||||
|
||||
if not target_user.is_mine:
|
||||
raise SynapseError(400, "Can only whois a local user")
|
||||
|
||||
ret = yield self.handlers.admin_handler.get_whois(auth_user)
|
||||
|
||||
defer.returnValue((200, ret))
|
||||
|
||||
|
||||
def register_servlets(hs, http_server):
|
||||
WhoisRestServlet(hs).register(http_server)
|
|
@ -294,18 +294,54 @@ class DataStore(RoomMemberStore, RoomStore,
|
|||
|
||||
defer.returnValue(self.min_token)
|
||||
|
||||
def insert_client_ip(self, user, access_token, ip, user_agent):
|
||||
def insert_client_ip(self, user, access_token, device_id, ip, user_agent):
|
||||
return self._simple_insert(
|
||||
"user_ips",
|
||||
{
|
||||
"user": user.to_string(),
|
||||
"access_token": access_token,
|
||||
"device_id": device_id,
|
||||
"ip": ip,
|
||||
"user_agent": user_agent,
|
||||
"last_used": int(self._clock.time()),
|
||||
"last_seen": int(self._clock.time_msec()),
|
||||
}
|
||||
)
|
||||
|
||||
def get_user_ip_and_agents(self, user):
|
||||
return self._simple_select_list(
|
||||
table="user_ips",
|
||||
keyvalues={"user": user.to_string()},
|
||||
retcols=[
|
||||
"device_id", "access_token", "ip", "user_agent", "last_seen"
|
||||
],
|
||||
)
|
||||
|
||||
d = {}
|
||||
for r in res:
|
||||
device = d.setdefault(r["device_id"], {})
|
||||
session = device.setdefault(r["access_token"], [])
|
||||
session.append({
|
||||
"ip": r["ip"],
|
||||
"user_agent": r["user_agent"],
|
||||
"last_seen": r["last_seen"],
|
||||
})
|
||||
|
||||
defer.returnValue(
|
||||
[
|
||||
{
|
||||
"device_id": k,
|
||||
"sessions": [
|
||||
{
|
||||
"access_token": x,
|
||||
"connections": y,
|
||||
}
|
||||
for x, y in v.items()
|
||||
]
|
||||
}
|
||||
for k, v in d.items()
|
||||
]
|
||||
)
|
||||
|
||||
def snapshot_room(self, room_id, user_id, state_type=None, state_key=None):
|
||||
"""Snapshot the room for an update by a user
|
||||
Args:
|
||||
|
|
|
@ -88,7 +88,6 @@ class RegistrationStore(SQLBaseStore):
|
|||
query, user_id
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def get_user_by_token(self, token):
|
||||
"""Get a user from the given access token.
|
||||
|
||||
|
@ -99,11 +98,11 @@ class RegistrationStore(SQLBaseStore):
|
|||
Raises:
|
||||
StoreError if no user was found.
|
||||
"""
|
||||
user_id = yield self.runInteraction(self._query_for_auth,
|
||||
token)
|
||||
defer.returnValue(user_id)
|
||||
return self.runInteraction(
|
||||
self._query_for_auth,
|
||||
token
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def is_server_admin(self, user):
|
||||
return self._simple_select_one_onecol(
|
||||
table="users",
|
||||
|
@ -112,11 +111,16 @@ class RegistrationStore(SQLBaseStore):
|
|||
)
|
||||
|
||||
def _query_for_auth(self, txn, token):
|
||||
txn.execute("SELECT users.name FROM access_tokens LEFT JOIN users" +
|
||||
" ON users.id = access_tokens.user_id WHERE token = ?",
|
||||
[token])
|
||||
row = txn.fetchone()
|
||||
if row:
|
||||
return row[0]
|
||||
sql = (
|
||||
"SELECT users.name, users.admin, access_tokens.device_id "
|
||||
"FROM users "
|
||||
"INNER JOIN access_tokens on users.id = access_tokens.user_id "
|
||||
"WHERE token = ?"
|
||||
)
|
||||
|
||||
cursor = txn.execute(sql, (token,))
|
||||
rows = self.cursor_to_dict(cursor)
|
||||
if rows:
|
||||
return rows[0]
|
||||
|
||||
raise StoreError(404, "Token not found.")
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
CREATE TABLE IF NOT EXISTS user_ips (
|
||||
user TEXT NOT NULL,
|
||||
access_token TEXT NOT NULL,
|
||||
device_id TEXT,
|
||||
ip TEXT NOT NULL,
|
||||
user_agent TEXT NOT NULL,
|
||||
last_used INTEGER NOT NULL,
|
||||
last_seen INTEGER NOT NULL,
|
||||
CONSTRAINT user_ip UNIQUE (user, access_token, ip, user_agent) ON CONFLICT REPLACE
|
||||
);
|
||||
|
||||
|
|
|
@ -34,9 +34,10 @@ CREATE TABLE IF NOT EXISTS access_tokens(
|
|||
CREATE TABLE IF NOT EXISTS user_ips (
|
||||
user TEXT NOT NULL,
|
||||
access_token TEXT NOT NULL,
|
||||
device_id TEXT,
|
||||
ip TEXT NOT NULL,
|
||||
user_agent TEXT NOT NULL,
|
||||
last_used INTEGER NOT NULL,
|
||||
last_seen INTEGER NOT NULL,
|
||||
CONSTRAINT user_ip UNIQUE (user, access_token, ip, user_agent) ON CONFLICT REPLACE
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in a new issue