Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin API implementation #253

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
116 changes: 116 additions & 0 deletions matrix_client/admin_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
from matrix_client.api import MatrixHttpApi
try:
basestring
except NameError:
basestring = str


class MatrixHttpAdminApi(MatrixHttpApi):
"""Extends Matrix API with admin calls.

Examples:
Create a client and send a message::

matrix = MatrixHttpAdminApi("https://matrix.org", token="foobar")
response = admin_api.shutdown_room(
"!DgvjtOljKujDBrxyHk:matrix.org",
"@admin:matrix.org",
room_name="New room",
message="Old room closed by admin"
)
"""
def purge_history(self, room_id, event_id):
"""Perform /admin/purge_hostory.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Admin api part.
Args:
room_id (str): Room_id to purge.
event_id (str or int): Event_id or ts to purge before.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply have two optional arguments? It would look clearer to me, and simplify the code.

"""
if isinstance(event_id, basestring):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not relevant if you choose to follow the comment above, but it looks like swapping the if/else blocks here would allow to test for isinstance(event_id, int) and get rid of the ugly basestring hack.

content = {
"delete_local_events": True,
"purge_up_to_event_id": event_id
}
else:
content = {
"delete_local_events": True,
"purge_up_to_ts": int(event_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the docstring, this is already an int. I don't think we want to try to catch user misuses, especially in API level methods.

}
return self._send("POST", "/admin/purge_history/%s" % room_id, content)

def purge_history_status(self, purge_id):
"""Perform /admin/purge_history_status.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a newline after this one (applies to most of the docstrings here).

Admin api part.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt this line is useful, but if you want to keep it it needs a newline after (applies to every docstrings).

Args:
purge_id (str): Purge_id to query status.
"""
return self._send("GET", "/admin/purge_history_status/%s" % purge_id)

def media_in_room(self, room_id, event_id=None):
"""List remote and local media in room.
Args:
room_id (str): Room_id to purge.
"""
return self._send("GET", "/admin/room/%s/media" % room_id)

def whois(self, user_id):
"""Query server for user information (ip, UA, last seen).
Admin api part.
Args:
user_id (str): user_id to query.
"""
return self._send("GET", "/admin/whois/%s" % user_id)

def deactivate(self, user_id, erase=False):
"""Deactivate user account.
Admin api part.
Args:
user_id (str): user_id to deactivate.
erase (bool): erase user data. Default no.
"""
content = {
"erase": erase
}
return self._send("POST", "/admin/deactivate/%s" % user_id, content)

def reset_password(self, user_id, password):
"""Reset users's password to provided.
Admin api part.
Args:
user_id (str): user_id to deactivate.
password (str): password to set.
"""
content = {
"new_password": password
}
return self._send("POST", "/admin/reset_password/%s" % user_id, content)

def quarantine_media(self, room_id):
"""Quarantine all media in room so that no one can download it via thi server.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

Admin api part.
Args:
room_id (str): room_id to quarantine.
"""
return self._send("POST", "/admin/quarantine_media/%s" % room_id)

def shutdown_room(self, room_id, new_room_user_id, room_name=False, message=False):
"""Shuts down a room by removing all local users from the room and blocking
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first line of a docstring should be a single sentence, ie not split across multiple lines.

all future invites and joins to the room. Any local aliases will be repointed
to a new room created by `new_room_user_id` and kicked users will be auto
joined to the new room
Admin api part.
Args:
room_id (str): room_id to quarantine.
new_room_user_id (str): new room creator user_id.
room_name (str): new room name.
message (str): information message for new room.
"""
content = {
"new_room_user_id": new_room_user_id
}
if room_name:
content["room_name"] = room_name
if message:
content["message"] = message
return self._send("POST", "/admin/shutdown_room/%s" % room_id, content)
169 changes: 169 additions & 0 deletions test/admin_api_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import responses
import json
from matrix_client.admin_api import MatrixHttpAdminApi


class TestAdminApi:
admin_api = MatrixHttpAdminApi("http://example.com")
user_id = "@alice:matrix.org"
room_id = "!gveUzqBzXPqmwvDaCZ:example.org"
event_id = "$153119074937XoqNn::example.org"
up_to_ts = 1531190749090
purge_id = "dLVEjckmfggyQduS"

@responses.activate
def test_purge_history_eventid(self):
purge_history_url = \
"http://example.com/_matrix/client/r0/" \
"admin/purge_history/%s" % self.room_id
responses.add(
responses.POST,
purge_history_url,
body='{"purge_id": "%s"}' % self.purge_id
)
self.admin_api.purge_history(self.room_id, self.event_id)
req = responses.calls[0].request
assert req.url == purge_history_url
assert req.method == 'POST'
j = json.loads(req.body)
assert j["delete_local_events"]
assert j["purge_up_to_event_id"] == self.event_id

@responses.activate
def test_purge_history_up_to_ts(self):
purge_history_url = \
"http://example.com/_matrix/client/r0/" \
"admin/purge_history/%s" % self.room_id
responses.add(
responses.POST,
purge_history_url,
body='{"purge_id": "%s"}' % self.purge_id
)
self.admin_api.purge_history(self.room_id, self.up_to_ts)
req = responses.calls[0].request
j = json.loads(req.body)
assert j["delete_local_events"]
assert j["purge_up_to_ts"] == self.up_to_ts

@responses.activate
def test_purge_history_status(self):
purge_history_status_url = \
"http://example.com/_matrix/client/r0/" \
"admin/purge_history_status/%s" % self.purge_id
responses.add(
responses.GET,
purge_history_status_url,
body='{"status": "complete"}'
)
self.admin_api.purge_history_status(self.purge_id)
req = responses.calls[0].request
assert req.url == purge_history_status_url

@responses.activate
def test_media_in_room(self):
media_url = \
"http://example.com/_matrix/client/r0/" \
"admin/room/%s/media" % self.room_id
responses.add(
responses.GET,
media_url,
body='{"local": ["mxc://example.com/xwvutsrqponmlkjihgfedcba"],'
' "remote": ["mxc://matrix.org/xwtttsrqponmlkjihgfedcba"]}'
)
resp = self.admin_api.media_in_room(self.room_id)
req = responses.calls[0].request
assert req.url == media_url
assert req.method == 'GET'
assert "local" in resp
assert "remote" in resp

@responses.activate
def test_whois(self):
whois_url = \
"http://example.com/_matrix/client/r0/" \
"admin/whois/%s" % self.user_id
responses.add(
responses.GET,
whois_url,
body='{"user_id": "%s", "devices": {}}' % self.user_id
)
self.admin_api.whois(self.user_id)
req = responses.calls[0].request
assert req.url == whois_url
assert req.method == 'GET'

@responses.activate
def test_deactivate_no_erase(self):
erase_url = \
"http://example.com/_matrix/client/r0/" \
"admin/deactivate/%s" % self.user_id
responses.add(responses.POST, erase_url, body='{}')
self.admin_api.deactivate(self.user_id)
req = responses.calls[0].request
assert req.url == erase_url
assert req.method == 'POST'

@responses.activate
def test_deactivate(self):
erase_url = \
"http://example.com/_matrix/client/r0/" \
"admin/deactivate/%s" % self.user_id
responses.add(responses.POST, erase_url, body='{}')
self.admin_api.deactivate(self.user_id, erase=True)
req = responses.calls[0].request
assert req.url == erase_url
assert req.method == 'POST'
j = json.loads(req.body)
assert j["erase"]

@responses.activate
def test_reset_password(self):
reset_url = \
"http://example.com/_matrix/client/r0/" \
"admin/reset_password/%s" % self.user_id
responses.add(responses.POST, reset_url, body='{}')
self.admin_api.reset_password(self.user_id, 'secret')
req = responses.calls[0].request
assert req.url == reset_url
assert req.method == 'POST'
j = json.loads(req.body)
assert j["new_password"] == 'secret'

@responses.activate
def test_quarantine_media(self):
quarantine_media_url = \
"http://example.com/_matrix/client/r0/" \
"admin/quarantine_media/%s" % self.room_id
responses.add(
responses.POST,
quarantine_media_url,
body='{"num_quarantined": 1}'
)
self.admin_api.quarantine_media(self.room_id)
req = responses.calls[0].request
assert req.url == quarantine_media_url
assert req.method == 'POST'

@responses.activate
def test_shutdown_room(self):
shutdown_room_url = \
"http://example.com/_matrix/client/r0/" \
"admin/shutdown_room/%s" % self.room_id
responses.add(
responses.POST,
shutdown_room_url,
body='{"kicked_users": 2, '
'"local_aliases": [], '
'"new_room_id": "!hepuyalbwtkjapqdhq:example.org"}'
)
self.admin_api.shutdown_room(
self.room_id,
self.user_id,
room_name="New room",
message="Old room closed by admin"
)
req = responses.calls[0].request
assert req.url == shutdown_room_url
assert req.method == 'POST'
j = json.loads(req.body)
assert j["new_room_user_id"] == self.user_id