diff --git a/contrib/scripts/kick_users.py b/contrib/scripts/kick_users.py
new file mode 100755
index 0000000000..5dfaec3ad0
--- /dev/null
+++ b/contrib/scripts/kick_users.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+from argparse import ArgumentParser
+import json
+import requests
+import sys
+import urllib
+
+def _mkurl(template, kws):
+ for key in kws:
+ template = template.replace(key, kws[key])
+ return template
+
+def main(hs, room_id, access_token, user_id_prefix, why):
+ if not why:
+ why = "Automated kick."
+ print "Kicking members on %s in room %s matching %s" % (hs, room_id, user_id_prefix)
+ room_state_url = _mkurl(
+ "$HS/_matrix/client/api/v1/rooms/$ROOM/state?access_token=$TOKEN",
+ {
+ "$HS": hs,
+ "$ROOM": room_id,
+ "$TOKEN": access_token
+ }
+ )
+ print "Getting room state => %s" % room_state_url
+ res = requests.get(room_state_url)
+ print "HTTP %s" % res.status_code
+ state_events = res.json()
+ if "error" in state_events:
+ print "FATAL"
+ print state_events
+ return
+
+ kick_list = []
+ room_name = room_id
+ for event in state_events:
+ if not event["type"] == "m.room.member":
+ if event["type"] == "m.room.name":
+ room_name = event["content"].get("name")
+ continue
+ if not event["content"].get("membership") == "join":
+ continue
+ if event["state_key"].startswith(user_id_prefix):
+ kick_list.append(event["state_key"])
+
+ if len(kick_list) == 0:
+ print "No user IDs match the prefix '%s'" % user_id_prefix
+ return
+
+ print "The following user IDs will be kicked from %s" % room_name
+ for uid in kick_list:
+ print uid
+ doit = raw_input("Continue? [Y]es\n")
+ if len(doit) > 0 and doit.lower() == 'y':
+ print "Kicking members..."
+ # encode them all
+ kick_list = [urllib.quote(uid) for uid in kick_list]
+ for uid in kick_list:
+ kick_url = _mkurl(
+ "$HS/_matrix/client/api/v1/rooms/$ROOM/state/m.room.member/$UID?access_token=$TOKEN",
+ {
+ "$HS": hs,
+ "$UID": uid,
+ "$ROOM": room_id,
+ "$TOKEN": access_token
+ }
+ )
+ kick_body = {
+ "membership": "leave",
+ "reason": why
+ }
+ print "Kicking %s" % uid
+ res = requests.put(kick_url, data=json.dumps(kick_body))
+ if res.status_code != 200:
+ print "ERROR: HTTP %s" % res.status_code
+ if res.json().get("error"):
+ print "ERROR: JSON %s" % res.json()
+
+
+
+if __name__ == "__main__":
+ parser = ArgumentParser("Kick members in a room matching a certain user ID prefix.")
+ parser.add_argument("-u","--user-id",help="The user ID prefix e.g. '@irc_'")
+ parser.add_argument("-t","--token",help="Your access_token")
+ parser.add_argument("-r","--room",help="The room ID to kick members in")
+ parser.add_argument("-s","--homeserver",help="The base HS url e.g. http://matrix.org")
+ parser.add_argument("-w","--why",help="Reason for the kick. Optional.")
+ args = parser.parse_args()
+ if not args.room or not args.token or not args.user_id or not args.homeserver:
+ parser.print_help()
+ sys.exit(1)
+ else:
+ main(args.homeserver, args.room, args.token, args.user_id, args.why)
diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index 18f3d117b3..e159e4503f 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -215,17 +215,20 @@ class Auth(object):
else:
ban_level = 50 # FIXME (erikj): What should we do here?
- if Membership.INVITE == membership:
- # TODO (erikj): We should probably handle this more intelligently
- # PRIVATE join rules.
-
- # Invites are valid iff caller is in the room and target isn't.
+ if Membership.JOIN != membership:
+ # JOIN is the only action you can perform if you're not in the room
if not caller_in_room: # caller isn't joined
raise AuthError(
403,
"%s not in room %s." % (event.user_id, event.room_id,)
)
- elif target_banned:
+
+ if Membership.INVITE == membership:
+ # TODO (erikj): We should probably handle this more intelligently
+ # PRIVATE join rules.
+
+ # Invites are valid iff caller is in the room and target isn't.
+ if target_banned:
raise AuthError(
403, "%s is banned from the room" % (target_user_id,)
)
@@ -251,13 +254,7 @@ class Auth(object):
raise AuthError(403, "You are not allowed to join this room")
elif Membership.LEAVE == membership:
# TODO (erikj): Implement kicks.
-
- if not caller_in_room: # trying to leave a room you aren't joined
- raise AuthError(
- 403,
- "%s not in room %s." % (target_user_id, event.room_id,)
- )
- elif target_banned and user_level < ban_level:
+ if target_banned and user_level < ban_level:
raise AuthError(
403, "You cannot unban user &s." % (target_user_id,)
)
diff --git a/synapse/handlers/typing.py b/synapse/handlers/typing.py
index c2762f92c7..c0b2bd7db0 100644
--- a/synapse/handlers/typing.py
+++ b/synapse/handlers/typing.py
@@ -223,6 +223,7 @@ class TypingNotificationEventSource(object):
def __init__(self, hs):
self.hs = hs
self._handler = None
+ self._room_member_handler = None
def handler(self):
# Avoid cyclic dependency in handler setup
@@ -230,6 +231,11 @@ class TypingNotificationEventSource(object):
self._handler = self.hs.get_handlers().typing_notification_handler
return self._handler
+ def room_member_handler(self):
+ if not self._room_member_handler:
+ self._room_member_handler = self.hs.get_handlers().room_member_handler
+ return self._room_member_handler
+
def _make_event_for(self, room_id):
typing = self.handler()._room_typing[room_id]
return {
@@ -240,19 +246,25 @@ class TypingNotificationEventSource(object):
},
}
+ @defer.inlineCallbacks
def get_new_events_for_user(self, user, from_key, limit):
from_key = int(from_key)
handler = self.handler()
+ joined_room_ids = (
+ yield self.room_member_handler().get_joined_rooms_for_user(user)
+ )
+
events = []
for room_id in handler._room_serials:
+ if room_id not in joined_room_ids:
+ continue
if handler._room_serials[room_id] <= from_key:
continue
- # TODO: check if user is in room
events.append(self._make_event_for(room_id))
- return (events, handler._latest_room_serial)
+ defer.returnValue((events, handler._latest_room_serial))
def get_current_key(self):
return self.handler()._latest_room_serial
diff --git a/tests/handlers/test_typing.py b/tests/handlers/test_typing.py
index 2d76b23564..b318d4944a 100644
--- a/tests/handlers/test_typing.py
+++ b/tests/handlers/test_typing.py
@@ -131,6 +131,13 @@ class TypingNotificationsTestCase(unittest.TestCase):
return defer.succeed([])
self.room_member_handler.get_room_members = get_room_members
+ def get_joined_rooms_for_user(user):
+ if user in self.room_members:
+ return defer.succeed([self.room_id])
+ else:
+ return defer.succeed([])
+ self.room_member_handler.get_joined_rooms_for_user = get_joined_rooms_for_user
+
@defer.inlineCallbacks
def fetch_room_distributions_into(room_id, localusers=None,
remotedomains=None, ignore_user=None):
@@ -180,8 +187,9 @@ class TypingNotificationsTestCase(unittest.TestCase):
])
self.assertEquals(self.event_source.get_current_key(), 1)
+ events = yield self.event_source.get_new_events_for_user(self.u_apple, 0, None)
self.assertEquals(
- self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
+ events[0],
[
{"type": "m.typing",
"room_id": self.room_id,
@@ -242,8 +250,9 @@ class TypingNotificationsTestCase(unittest.TestCase):
])
self.assertEquals(self.event_source.get_current_key(), 1)
+ events = yield self.event_source.get_new_events_for_user(self.u_apple, 0, None)
self.assertEquals(
- self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
+ events[0],
[
{"type": "m.typing",
"room_id": self.room_id,
@@ -297,8 +306,9 @@ class TypingNotificationsTestCase(unittest.TestCase):
yield put_json.await_calls()
self.assertEquals(self.event_source.get_current_key(), 1)
+ events = yield self.event_source.get_new_events_for_user(self.u_apple, 0, None)
self.assertEquals(
- self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
+ events[0],
[
{"type": "m.typing",
"room_id": self.room_id,
@@ -327,8 +337,9 @@ class TypingNotificationsTestCase(unittest.TestCase):
self.on_new_user_event.reset_mock()
self.assertEquals(self.event_source.get_current_key(), 1)
+ events = yield self.event_source.get_new_events_for_user(self.u_apple, 0, None)
self.assertEquals(
- self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
+ events[0],
[
{"type": "m.typing",
"room_id": self.room_id,
@@ -345,8 +356,9 @@ class TypingNotificationsTestCase(unittest.TestCase):
])
self.assertEquals(self.event_source.get_current_key(), 2)
+ events = yield self.event_source.get_new_events_for_user(self.u_apple, 1, None)
self.assertEquals(
- self.event_source.get_new_events_for_user(self.u_apple, 1, None)[0],
+ events[0],
[
{"type": "m.typing",
"room_id": self.room_id,
@@ -371,8 +383,9 @@ class TypingNotificationsTestCase(unittest.TestCase):
self.on_new_user_event.reset_mock()
self.assertEquals(self.event_source.get_current_key(), 3)
+ events = yield self.event_source.get_new_events_for_user(self.u_apple, 0, None)
self.assertEquals(
- self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
+ events[0],
[
{"type": "m.typing",
"room_id": self.room_id,
diff --git a/tests/rest/client/v1/test_typing.py b/tests/rest/client/v1/test_typing.py
index 80f2ec9ddf..7b3bd87439 100644
--- a/tests/rest/client/v1/test_typing.py
+++ b/tests/rest/client/v1/test_typing.py
@@ -34,6 +34,8 @@ class RoomTypingTestCase(RestTestCase):
""" Tests /rooms/$room_id/typing/$user_id REST API. """
user_id = "@sid:red"
+ user = UserID.from_string(user_id)
+
@defer.inlineCallbacks
def setUp(self):
self.clock = MockClock()
@@ -75,7 +77,7 @@ class RoomTypingTestCase(RestTestCase):
def get_room_members(room_id):
if room_id == self.room_id:
- return defer.succeed([UserID.from_string(self.user_id)])
+ return defer.succeed([self.user])
else:
return defer.succeed([])
@@ -115,8 +117,9 @@ class RoomTypingTestCase(RestTestCase):
self.assertEquals(200, code)
self.assertEquals(self.event_source.get_current_key(), 1)
+ events = yield self.event_source.get_new_events_for_user(self.user, 0, None)
self.assertEquals(
- self.event_source.get_new_events_for_user(self.user_id, 0, None)[0],
+ events[0],
[
{"type": "m.typing",
"room_id": self.room_id,
|