class CacheRedis(object): redis = None def __init__(self, env, host: str, port: int = 6379, db: int = 0): if env.config.get(ConfigKeys.TESTING, False) or host == 'mock': from fakeredis import FakeStrictRedis as Redis else: from redis import Redis self.redis = Redis(host=host, port=port, db=db) self.cache = MemoryCache() def _flushall(self): self.redis.flushdb() self.cache.flushall() def _set(self, key, val, ttl=None): if ttl is None: self.cache.set(key, val) else: self.cache.set(key, val, ttl=ttl) def _get(self, key): return self.cache.get(key) def _del(self, key): self.cache.delete(key) def set_is_room_ephemeral(self, room_id: str, is_ephemeral: bool) -> None: redis_key = RedisKeys.non_ephemeral_rooms() cache_key = '%s-%s' % (redis_key, room_id) self.cache.set(cache_key, is_ephemeral) def is_room_ephemeral(self, room_id: str) -> bool: redis_key = RedisKeys.non_ephemeral_rooms() cache_key = '%s-%s' % (redis_key, room_id) return self.cache.get(cache_key) def get_black_list(self) -> set: cache_key = RedisKeys.black_list() value = self.cache.get(cache_key) if value is not None: return value values = self.redis.smembers(cache_key) if value is not None: decoded = {str(v, 'utf-8') for v in values} self.cache.set(cache_key, decoded, ttl=60 * 10) return decoded return None def set_black_list(self, the_list: set) -> None: cache_key = RedisKeys.black_list() self.cache.set(cache_key, the_list, ttl=60 * 10) self.redis.delete(cache_key) self.redis.sadd(cache_key, *the_list) def remove_from_black_list(self, word: str) -> None: cache_key = RedisKeys.black_list() the_cached_list = self.cache.get(cache_key) the_cached_list.remove(word) self.cache.set(cache_key, the_cached_list, ttl=60 * 10) self.redis.srem(cache_key, word) def add_to_black_list(self, word: str) -> None: cache_key = RedisKeys.black_list() the_cached_list = self.cache.get(cache_key) the_cached_list.add(word) self.cache.set(cache_key, the_cached_list, ttl=60 * 10) self.redis.sadd(cache_key, word) def _set_ban_timestamp(self, key: str, user_id: str, timestamp: str) -> None: cache_key = '%s-%s' % (key, user_id) self.cache.set(cache_key, timestamp) self.redis.hset(key, user_id, timestamp) def set_global_ban_timestamp(self, user_id: str, duration: str, timestamp: str, username: str) -> None: key = RedisKeys.banned_users() self._set_ban_timestamp(key, user_id, '%s|%s|%s' % (duration, timestamp, username)) def set_channel_ban_timestamp(self, channel_id: str, user_id: str, duration: str, timestamp: str, username: str) -> None: key = RedisKeys.banned_users_channel(channel_id) self._set_ban_timestamp(key, user_id, '%s|%s|%s' % (duration, timestamp, username)) def set_room_ban_timestamp(self, room_id: str, user_id: str, duration: str, timestamp: str, username: str) -> None: key = RedisKeys.banned_users(room_id) self._set_ban_timestamp(key, user_id, '%s|%s|%s' % (duration, timestamp, username)) def get_user_roles(self, user_id: str) -> None: key = RedisKeys.user_roles() cache_key = '%s-%s' % (key, user_id) value = self.cache.get(cache_key) if value is not None: return value value = self.redis.hget(key, user_id) if value is not None: value = json.loads(str(value, 'utf-8')) self.cache.set(cache_key, value, ttl=10) return value def set_user_roles(self, user_id: str, roles: dict) -> None: key = RedisKeys.user_roles() cache_key = '%s-%s' % (key, user_id) self.redis.hset(key, user_id, json.dumps(roles)) self.cache.set(cache_key, roles, ttl=10) def reset_user_roles(self, user_id: str) -> None: key = RedisKeys.user_roles() cache_key = '%s-%s' % (key, user_id) self.redis.hdel(key, user_id) self.cache.delete(cache_key) def get_admin_room(self) -> str: key = RedisKeys.admin_room() value = self.cache.get(key) if value is not None: return value room_id = self.redis.get(key) if room_id is None or len(str(room_id, 'utf-8').strip()) == 0: return None room_id = str(room_id, 'utf-8') self.cache.set(key, room_id, ttl=EIGHT_HOURS_IN_SECONDS) return room_id def set_admin_room(self, room_id: str) -> None: key = RedisKeys.admin_room() self.redis.set(key, room_id) self.cache.set(key, room_id, ttl=EIGHT_HOURS_IN_SECONDS) def _get_ban_timestamp(self, key: str, user_id: str) -> str: cache_key = '%s-%s' % (key, user_id) value = self.cache.get(cache_key) if value is not None: return value.split('|', 2) ban_info = self.redis.hget(key, user_id) if ban_info is None: return None, None, None ban_info = str(ban_info, 'utf-8') return ban_info.split('|', 2) def get_global_ban_timestamp(self, user_id: str) -> str: key = RedisKeys.banned_users() return self._get_ban_timestamp(key, user_id) def get_channel_ban_timestamp(self, channel_id: str, user_id: str) -> str: key = RedisKeys.banned_users_channel(channel_id) return self._get_ban_timestamp(key, user_id) def get_room_ban_timestamp(self, room_id: str, user_id: str) -> str: key = RedisKeys.banned_users(room_id) return self._get_ban_timestamp(key, user_id) def get_room_id_for_name(self, channel_id: str, room_name: str) -> str: key = RedisKeys.room_id_for_name(channel_id) cache_key = '%s-%s' % (key, room_name) value = self.cache.get(cache_key) if value is not None: return value value = self.redis.hget(key, room_name) if value is None: return None value = str(value, 'utf-8') self.cache.set(cache_key, value) return value def set_room_id_for_name(self, channel_id, room_name, room_id): key = RedisKeys.room_id_for_name(channel_id) cache_key = '%s-%s' % (key, room_name) self.cache.set(cache_key, room_id) self.redis.hset(key, room_name, room_id) def get_user_name(self, user_id: str) -> str: key = RedisKeys.user_names() cache_key = '%s-%s' % (key, user_id) value = self.cache.get(cache_key) if value is not None: return value user_name = self.redis.hget(key, user_id) if user_name is not None: user_name = str(user_name, 'utf-8') self.cache.set(cache_key, user_name) return user_name return user_name def set_user_name(self, user_id: str, user_name: str): key = RedisKeys.user_names() cache_key = '%s-%s' % (key, user_id) self.redis.hset(key, user_id, user_name) self.cache.set(cache_key, user_name) def get_room_exists(self, channel_id, room_id): key = RedisKeys.rooms(channel_id) cache_key = '%s-%s' % (key, room_id) value = self.cache.get(cache_key) if value is not None: return True exists = self.redis.hexists(key, room_id) if exists == 1: self.cache.set(cache_key, True) return True return None def remove_room_exists(self, channel_id, room_id): key = RedisKeys.rooms(channel_id) cache_key = '%s-%s' % (key, room_id) self.cache.set(cache_key, None) self.redis.hdel(key, room_id) def set_room_exists(self, channel_id, room_id, room_name): key = RedisKeys.rooms(channel_id) cache_key = '%s-%s' % (key, room_id) self.cache.set(cache_key, room_name) self.redis.hset(key, room_id, room_name) def set_channel_exists(self, channel_id: str) -> None: key = RedisKeys.channel_exists() cache_key = '%s-%s' % (key, channel_id) self.redis.hset(key, channel_id, True) self.cache.set(cache_key, True) def set_channel_for_room(self, channel_id: str, room_id: str) -> None: key = RedisKeys.channel_for_rooms() cache_key = '%s-%s' % (key, room_id) self.redis.hset(key, room_id, channel_id) self.cache.set(cache_key, channel_id) def get_channel_exists(self, channel_id): key = RedisKeys.channel_exists() cache_key = '%s-%s' % (key, channel_id) value = self.cache.get(cache_key) if value is not None: return True value = self.redis.hget(key, channel_id) if value is None: return None self.cache.set(cache_key, True) return True def set_channel_name(self, channel_id: str, channel_name: str) -> None: key = RedisKeys.channels() cache_key = '%s-name-%s' % (key, channel_id) self.cache.set(cache_key, channel_name) self.redis.hset(key, channel_id, channel_name) def get_channel_name(self, channel_id: str) -> str: key = RedisKeys.channels() cache_key = '%s-name-%s' % (key, channel_id) value = self.cache.get(cache_key) if value is not None: return value value = self.redis.hget(key, channel_id) if value is None: return None value = str(value, 'utf-8') self.cache.set(cache_key, value) return value def get_room_name(self, room_id: str) -> str: key = RedisKeys.room_name_for_id() cache_key = '%s-%s-name' % (key, room_id) value = self.cache.get(cache_key) if value is not None: return value value = self.redis.hget(key, room_id) if value is None: return None value = str(value, 'utf-8') self.cache.set(cache_key, value) return value def get_channel_for_room(self, room_id): key = RedisKeys.channel_for_rooms() cache_key = '%s-%s' % (key, room_id) value = self.cache.get(key) if value is not None: return value channel_id = self.redis.hget(key, room_id) if channel_id is None: return None channel_id = str(channel_id, 'utf-8') self.cache.set(cache_key, channel_id) return channel_id def get_user_status(self, user_id: str): key = RedisKeys.user_status(user_id) value = self.cache.get(key) if value is not None: return value status = self.redis.get(key) if status is None or status == '': return None user_status = str(status, 'utf-8') self.cache.set(key, user_status) return user_status def set_user_status(self, user_id: str, status: str) -> None: key = RedisKeys.user_status(user_id) self.redis.set(key, status) self.cache.set(key, status) def user_check_status(self, user_id, other_status): return self.get_user_status(user_id) == other_status def user_is_offline(self, user_id): return self.user_check_status(user_id, UserKeys.STATUS_UNAVAILABLE) def user_is_online(self, user_id): return self.user_check_status(user_id, UserKeys.STATUS_AVAILABLE) def user_is_invisible(self, user_id): return self.user_check_status(user_id, UserKeys.STATUS_INVISIBLE) def set_user_offline(self, user_id: str) -> None: self.cache.set(RedisKeys.user_status(user_id), UserKeys.STATUS_UNAVAILABLE) self.redis.setbit(RedisKeys.online_bitmap(), int(user_id), 0) self.redis.srem(RedisKeys.online_set(), int(user_id)) self.redis.srem(RedisKeys.users_multi_cast(), user_id) self.redis.set(RedisKeys.user_status(user_id), UserKeys.STATUS_UNAVAILABLE) def set_user_online(self, user_id: str) -> None: self.cache.set(RedisKeys.user_status(user_id), UserKeys.STATUS_AVAILABLE) self.redis.setbit(RedisKeys.online_bitmap(), int(user_id), 1) self.redis.sadd(RedisKeys.online_set(), int(user_id)) self.redis.sadd(RedisKeys.users_multi_cast(), user_id) self.redis.set(RedisKeys.user_status(user_id), UserKeys.STATUS_AVAILABLE) def set_user_invisible(self, user_id: str) -> None: self.cache.set(RedisKeys.user_status(user_id), UserKeys.STATUS_INVISIBLE) self.redis.setbit(RedisKeys.online_bitmap(), int(user_id), 0) self.redis.srem(RedisKeys.online_set(), int(user_id)) self.redis.sadd(RedisKeys.users_multi_cast(), user_id) self.redis.set(RedisKeys.user_status(user_id), UserKeys.STATUS_INVISIBLE)
class TestStart(_TestOnPost): # noinspection PyAttributeOutsideInit def before(self): super(TestStart, self).before() self.redis = FakeStrictRedis() self.resource = Start(self.redis) self.api.add_route('/start/{game_id}', self.resource) def after(self): self.redis.flushall() del self.redis def test_without_userinfo(self): body = self.simulate_request('/start/1', decode='utf-8', method='POST') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('run_id', data, 'run_id has been set') self.assertIsNone(self.redis.hget('game:1:run', data['run_id']), 'redis run hash should be empty') self.assertIsNone(self.redis.hget('game:1:start', data['run_id']), 'redis start hash should be empty') def test_with_uid(self): _uid = 25 query_string = urlencode({ 'uid': _uid, 'userinfo': 'nothing', 'hash': '1234' }) body = self.simulate_request('/start/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('run_id', data, 'run_id has been set') self.assertIn('uid', data, 'uid has been set') self.assertEquals(_uid, data['uid'], 'use the old uid') self.assertEquals(str(data['uid']), self.redis.hget('game:1:run', data['run_id']), 'store run_id and uid in redis') self.assertIsNone(self.redis.hget('game:1:userinfo', data['uid']), 'should not save userinfo') self.assertTrue(self.redis.hexists('game:1:start', data['run_id']), 'should record start time') def test_with_userinfo_only(self): query_string = urlencode( {'userinfo': json.dumps({ 'a': '1', 'b': '2' })}) body = self.simulate_request('/start/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_400, self.srmock.status) data = json.loads(body) self.assertEquals('Missing parameter', data['title']) def test_with_userinfo_and_hash(self): _hash = 105 _userinfo = json.dumps({'field1': u'王思聪', 'field2': '15888888888'}) query_string = urlencode({'userinfo': _userinfo, 'hash': _hash}) body = self.simulate_request('/start/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('run_id', data, 'run_id has been set') self.assertIn('uid', data, 'uid has been set') self.assertEquals(_hash, data['uid'], 'use the old uid') self.assertEquals(str(data['uid']), self.redis.hget('game:1:run', data['run_id']), 'store run_id and uid in redis') self.assertEquals(_userinfo, self.redis.hget('game:1:userinfo', data['uid']), 'store userinfo and uid in redis') self.assertTrue(self.redis.hexists('game:1:start', data['run_id']), 'should record start time') def test_with_userinfo_and_hash_collision(self): _hash = 105 _userinfo = json.dumps({'field1': u'王思聪', 'field2': '15888888888'}) query_string = urlencode({'userinfo': _userinfo, 'hash': _hash}) self.redis.hset('game:1:userinfo', _hash, 'something different') body = self.simulate_request('/start/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('run_id', data, 'run_id has been set') self.assertIn('uid', data, 'uid has been set') self.assertNotEquals( _hash, data['uid'], 'generate a new uid different from original hash') self.assertEquals(str(data['uid']), self.redis.hget('game:1:run', data['run_id']), 'store run_id and uid in redis') self.assertEquals(_userinfo, self.redis.hget('game:1:userinfo', data['uid']), 'store userinfo and uid in redis') self.assertTrue(self.redis.hexists('game:1:start', data['run_id']), 'should record start time') def test_with_used_userinfo_and_hash(self): _hash = 105 _userinfo = json.dumps({'field1': u'王思聪', 'field2': '15888888888'}) query_string = urlencode({'userinfo': _userinfo, 'hash': _hash}) self.redis.hset('game:1:userinfo', _hash, _userinfo) body = self.simulate_request('/start/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('run_id', data, 'run_id has been set') self.assertIn('uid', data, 'uid has been set') self.assertEquals(_hash, data['uid'], 'use the original hash as uid') self.assertEquals(str(data['uid']), self.redis.hget('game:1:run', data['run_id']), 'store run_id and uid in redis') self.assertEquals(_userinfo, self.redis.hget('game:1:userinfo', data['uid']), 'store userinfo and uid in redis') self.assertTrue(self.redis.hexists('game:1:start', data['run_id']), 'should record start time')
class TestEnd(_TestOnPost): # noinspection PyAttributeOutsideInit def before(self): super(TestEnd, self).before() self.redis = FakeStrictRedis() self.redis.zadd('game:1:scores', 100, 100) self.redis.zadd('game:1:scores', 110, 110) self.resource = End(self.redis) self.api.add_route('/end/{game_id}', self.resource) self.api.req_options.auto_parse_form_urlencoded = True def after(self): self.redis.flushall() del self.redis def test_without_uid(self): _run_id = 'some_random_thing' _score = 105 query_string = urlencode({'run_id': _run_id, 'score': _score}) body = self.simulate_request('/end/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('rank', data, 'rank has been set') self.assertNotIn('best_score', data, 'no best_score') self.assertNotIn('best_rank', data, 'no best_rank') self.assertEquals(1, data['rank'], 'rank should be 1') self.assertEquals(str(_score), self.redis.hget('game:1:final', _run_id)) self.assertTrue(self.redis.hexists('game:1:end', _run_id), 'should record end time') def test_with_uid(self): _run_id = 'some_random_thing' _score = 105 _uid = 15 query_string = urlencode({ 'run_id': _run_id, 'score': _score, 'uid': _uid }) body = self.simulate_request('/end/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('rank', data, 'rank has been set') self.assertIn('best_score', data, 'best_score has been set') self.assertIn('best_rank', data, 'best_rank has been set') self.assertEquals(1, data['rank'], 'rank should be 1') self.assertEquals(_score, data['best_score'], 'score should be set as best score') self.assertEquals(1, data['best_rank'], 'best_rank should be 1') self.assertEquals(str(_score), self.redis.hget('game:1:final', _run_id)) self.assertTrue(self.redis.hexists('game:1:end', _run_id), 'should record end time') def test_update_best_records(self): _run_id = 'some_random_thing' _score = 105 _uid = 15 query_string = urlencode({ 'run_id': _run_id, 'score': _score, 'uid': _uid }) self.redis.hset('game:1:record:scores', _uid, 100) self.redis.hset('game:1:record:ranks', _uid, 2) body = self.simulate_request('/end/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('rank', data, 'rank has been set') self.assertIn('best_score', data, 'best_score has been set') self.assertIn('best_rank', data, 'best_rank has been set') self.assertEquals(1, data['rank'], 'rank should be 1') self.assertEquals(_score, data['best_score'], 'score should be set as best score') self.assertEquals(1, data['best_rank'], 'best_rank should be 1') self.assertEquals(str(_score), self.redis.hget('game:1:final', _run_id)) self.assertTrue(self.redis.hexists('game:1:end', _run_id), 'should record end time') def test_oot_update(self): _run_id = 'some_random_thing' _score = 105 _uid = 15 query_string = urlencode({ 'run_id': _run_id, 'score': _score, 'uid': _uid }) self.redis.hset('game:1:record:scores', _uid, 150) self.redis.hset('game:1:record:ranks', _uid, 0) body = self.simulate_request('/end/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('rank', data, 'rank has been set') self.assertIn('best_score', data, 'best_score has been set') self.assertIn('best_rank', data, 'best_rank has been set') self.assertEquals(1, data['rank'], 'rank should be 1') self.assertEquals(150, data['best_score'], 'score should be 150') self.assertEquals(0, data['best_rank'], 'best_rank should be 0') self.assertEquals(str(_score), self.redis.hget('game:1:final', _run_id)) self.assertTrue(self.redis.hexists('game:1:end', _run_id), 'should record end time')
class TestStart(_TestOnPost): # noinspection PyAttributeOutsideInit def before(self): super(TestStart, self).before() self.redis = FakeStrictRedis() self.resource = Start(self.redis) self.api.add_route('/start/{game_id}', self.resource) def after(self): self.redis.flushall() del self.redis def test_without_userinfo(self): body = self.simulate_request('/start/1', decode='utf-8', method='POST') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('run_id', data, 'run_id has been set') self.assertIsNone(self.redis.hget('game:1:run', data['run_id']), 'redis run hash should be empty') self.assertIsNone(self.redis.hget('game:1:start', data['run_id']), 'redis start hash should be empty') def test_with_uid(self): _uid = 25 query_string = urlencode({'uid': _uid, 'userinfo': 'nothing', 'hash': '1234'}) body = self.simulate_request('/start/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('run_id', data, 'run_id has been set') self.assertIn('uid', data, 'uid has been set') self.assertEquals(_uid, data['uid'], 'use the old uid') self.assertEquals(str(data['uid']), self.redis.hget('game:1:run', data['run_id']), 'store run_id and uid in redis') self.assertIsNone(self.redis.hget('game:1:userinfo', data['uid']), 'should not save userinfo') self.assertTrue(self.redis.hexists('game:1:start', data['run_id']), 'should record start time') def test_with_userinfo_only(self): query_string = urlencode({'userinfo': json.dumps({'a': '1', 'b': '2'})}) body = self.simulate_request('/start/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_400, self.srmock.status) data = json.loads(body) self.assertEquals('Missing parameter', data['title']) def test_with_userinfo_and_hash(self): _hash = 105 _userinfo = json.dumps({'field1': u'王思聪', 'field2': '15888888888'}) query_string = urlencode({'userinfo': _userinfo, 'hash': _hash}) body = self.simulate_request('/start/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('run_id', data, 'run_id has been set') self.assertIn('uid', data, 'uid has been set') self.assertEquals(_hash, data['uid'], 'use the old uid') self.assertEquals(str(data['uid']), self.redis.hget('game:1:run', data['run_id']), 'store run_id and uid in redis') self.assertEquals(_userinfo, self.redis.hget('game:1:userinfo', data['uid']), 'store userinfo and uid in redis') self.assertTrue(self.redis.hexists('game:1:start', data['run_id']), 'should record start time') def test_with_userinfo_and_hash_collision(self): _hash = 105 _userinfo = json.dumps({'field1': u'王思聪', 'field2': '15888888888'}) query_string = urlencode({'userinfo': _userinfo, 'hash': _hash}) self.redis.hset('game:1:userinfo', _hash, 'something different') body = self.simulate_request('/start/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('run_id', data, 'run_id has been set') self.assertIn('uid', data, 'uid has been set') self.assertNotEquals(_hash, data['uid'], 'generate a new uid different from original hash') self.assertEquals(str(data['uid']), self.redis.hget('game:1:run', data['run_id']), 'store run_id and uid in redis') self.assertEquals(_userinfo, self.redis.hget('game:1:userinfo', data['uid']), 'store userinfo and uid in redis') self.assertTrue(self.redis.hexists('game:1:start', data['run_id']), 'should record start time') def test_with_used_userinfo_and_hash(self): _hash = 105 _userinfo = json.dumps({'field1': u'王思聪', 'field2': '15888888888'}) query_string = urlencode({'userinfo': _userinfo, 'hash': _hash}) self.redis.hset('game:1:userinfo', _hash, _userinfo) body = self.simulate_request('/start/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('run_id', data, 'run_id has been set') self.assertIn('uid', data, 'uid has been set') self.assertEquals(_hash, data['uid'], 'use the original hash as uid') self.assertEquals(str(data['uid']), self.redis.hget('game:1:run', data['run_id']), 'store run_id and uid in redis') self.assertEquals(_userinfo, self.redis.hget('game:1:userinfo', data['uid']), 'store userinfo and uid in redis') self.assertTrue(self.redis.hexists('game:1:start', data['run_id']), 'should record start time')
class TestEnd(_TestOnPost): # noinspection PyAttributeOutsideInit def before(self): super(TestEnd, self).before() self.redis = FakeStrictRedis() self.redis.zadd('game:1:scores', 100, 100) self.redis.zadd('game:1:scores', 110, 110) self.resource = End(self.redis) self.api.add_route('/end/{game_id}', self.resource) self.api.req_options.auto_parse_form_urlencoded = True def after(self): self.redis.flushall() del self.redis def test_without_uid(self): _run_id = 'some_random_thing' _score = 105 query_string = urlencode({'run_id': _run_id, 'score': _score}) body = self.simulate_request('/end/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('rank', data, 'rank has been set') self.assertNotIn('best_score', data, 'no best_score') self.assertNotIn('best_rank', data, 'no best_rank') self.assertEquals(1, data['rank'], 'rank should be 1') self.assertEquals(str(_score), self.redis.hget('game:1:final', _run_id)) self.assertTrue(self.redis.hexists('game:1:end', _run_id), 'should record end time') def test_with_uid(self): _run_id = 'some_random_thing' _score = 105 _uid = 15 query_string = urlencode({'run_id': _run_id, 'score': _score, 'uid': _uid}) body = self.simulate_request('/end/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('rank', data, 'rank has been set') self.assertIn('best_score', data, 'best_score has been set') self.assertIn('best_rank', data, 'best_rank has been set') self.assertEquals(1, data['rank'], 'rank should be 1') self.assertEquals(_score, data['best_score'], 'score should be set as best score') self.assertEquals(1, data['best_rank'], 'best_rank should be 1') self.assertEquals(str(_score), self.redis.hget('game:1:final', _run_id)) self.assertTrue(self.redis.hexists('game:1:end', _run_id), 'should record end time') def test_update_best_records(self): _run_id = 'some_random_thing' _score = 105 _uid = 15 query_string = urlencode({'run_id': _run_id, 'score': _score, 'uid': _uid}) self.redis.hset('game:1:record:scores', _uid, 100) self.redis.hset('game:1:record:ranks', _uid, 2) body = self.simulate_request('/end/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('rank', data, 'rank has been set') self.assertIn('best_score', data, 'best_score has been set') self.assertIn('best_rank', data, 'best_rank has been set') self.assertEquals(1, data['rank'], 'rank should be 1') self.assertEquals(_score, data['best_score'], 'score should be set as best score') self.assertEquals(1, data['best_rank'], 'best_rank should be 1') self.assertEquals(str(_score), self.redis.hget('game:1:final', _run_id)) self.assertTrue(self.redis.hexists('game:1:end', _run_id), 'should record end time') def test_oot_update(self): _run_id = 'some_random_thing' _score = 105 _uid = 15 query_string = urlencode({'run_id': _run_id, 'score': _score, 'uid': _uid}) self.redis.hset('game:1:record:scores', _uid, 150) self.redis.hset('game:1:record:ranks', _uid, 0) body = self.simulate_request('/end/1', method='POST', query_string=query_string, decode='utf-8') self.assertEquals(falcon.HTTP_200, self.srmock.status) data = json.loads(body) self.assertIn('rank', data, 'rank has been set') self.assertIn('best_score', data, 'best_score has been set') self.assertIn('best_rank', data, 'best_rank has been set') self.assertEquals(1, data['rank'], 'rank should be 1') self.assertEquals(150, data['best_score'], 'score should be 150') self.assertEquals(0, data['best_rank'], 'best_rank should be 0') self.assertEquals(str(_score), self.redis.hget('game:1:final', _run_id)) self.assertTrue(self.redis.hexists('game:1:end', _run_id), 'should record end time')
class DatabaseRedis(object): redis = None def __init__(self, env: GNEnvironment, host: str, port: int = 6379, db: int = 0): if environ.env.config.get(ConfigKeys.TESTING, False) or host == 'mock': from fakeredis import FakeStrictRedis as Redis else: from redis import Redis self.env = env self.redis = Redis(host=host, port=port, db=db) self.acl_validator = AclValidator() def get_all_permanent_rooms(self): pass def get_room_acls_for_action(self, action) -> Dict[str, Dict[str, str]]: pass def set_all_rooms(self, all_rooms): pass def get_all_rooms(self) -> Union[List, None]: return list() def get_rooms_with_sid(self, user_id: str): pass def remove_sid_for_user_in_room(self, user_id, room_id, sid_to_remove): pass def sids_for_user_in_room(self, user_id, room_id): pass def get_rooms_with_sid(self, user_id: str): pass def remove_sid_for_user_in_room(self, user_id, room_id, sid_to_remove): pass def sids_for_user_in_room(self, user_id, room_id) -> set: return set() def get_user_for_sid(self, sid: str) -> str: return None def update_spam_config(self, enabled, max_length, min_length, should_delete, should_save) -> None: return def set_spam_min_length(self, min_length: int) -> None: return def set_spam_max_length(self, max_length: int) -> None: return def enable_spam_delete(self) -> None: return def disable_spam_delete(self) -> None: return def enable_spam_save(self) -> None: return def disable_spam_save(self) -> None: return def mark_spam_deleted_if_exists(self, message_id: str) -> None: return def mark_spam_not_deleted_if_exists(self, message_id: str) -> None: return def get_latest_spam(self, limit: int) -> list: return list() def get_spam(self, spam_id: int) -> dict: return dict() def get_spam_for_time_slice(self, room_id, user_id, from_time_int, to_time_int) -> list: return list() def get_spam_from(self, user_id: str) -> list: return list() def init_config(self) -> None: return def get_service_config(self, session=None) -> dict: return dict() def enable_spam_classifier(self) -> None: return def disable_spam_classifier(self) -> None: return def set_spam_correct_or_not(self, spam_id: int, correct: bool): pass # not supported def save_spam_prediction(self, activity: Activity, message, y_hats: tuple): pass # not supported def set_ephemeral_room(self, room_id: str): self.redis.srem(RedisKeys.non_ephemeral_rooms(), room_id) def unset_ephemeral_room(self, room_id: str): self.redis.sadd(RedisKeys.non_ephemeral_rooms(), room_id) def is_room_ephemeral(self, room_id: str) -> bool: return not self.redis.sismember(RedisKeys.non_ephemeral_rooms(), room_id) def add_words_to_blacklist(self, words: list) -> None: self.redis.sadd(RedisKeys.black_list(), words) def get_users_roles(self, user_ids: list) -> None: raise NotImplementedError('not available in redis implementation of db interface') def get_all_user_ids(self) -> list: raise NotImplementedError('not available in redis implementation of db interface') def remove_word_from_blacklist(self, word_id) -> None: raise NotImplementedError('not available in redis implementation of db interface') def remove_matching_word_from_blacklist(self, word: str) -> None: raise NotImplementedError('not available in redis implementation of db interface') def get_black_list_with_ids(self, session=None) -> list: raise NotImplementedError('not available in redis implementation of db interface') def get_black_list(self) -> set: values = self.redis.smembers(RedisKeys.black_list()) return {str(value, 'utf-8') for value in values} def search_for_users(self, query: str) -> list: raise NotImplementedError('not implemented in redis db backend') def get_user_roles_in_room(self, user_id: str, room_id: str) -> list: roles = self.get_user_roles(user_id) if room_id in roles['room']: return roles['room'][room_id] return list() def get_user_roles(self, user_id: str) -> dict: output = { 'global': list(), 'channel': dict(), 'room': dict() } checked_channels = set() rooms = self.redis.hgetall(RedisKeys.rooms_for_user(user_id)) global_roles = self.redis.hget(RedisKeys.global_roles(), user_id) if global_roles is not None: global_roles = str(global_roles, 'utf-8') output['global'] = [a for a in global_roles.split(',')] for room_id, _ in rooms.items(): room_id = str(room_id, 'utf-8') channel_id = self.channel_for_room(room_id) room_roles = self.redis.hget(RedisKeys.room_roles(room_id), user_id) if channel_id not in checked_channels: checked_channels.add(channel_id) channel_roles = self.redis.hget(RedisKeys.channel_roles(channel_id), user_id) if channel_roles is not None: channel_roles = str(channel_roles, 'utf-8') output['channel'][channel_id] = [a for a in channel_roles.split(',')] if room_roles is not None: room_roles = str(room_roles, 'utf-8') output['room'][room_id] = [a for a in room_roles.split(',')] return output def get_admins_in_room(self, room_id: str): return list() def get_online_admins(self) -> list: admins = self.get_super_users() return [ user_id for user_id, status in zip( admins.keys(), [self.get_user_status(user_id) for user_id in admins.keys()] ) if status in [ UserKeys.STATUS_AVAILABLE, UserKeys.STATUS_CHAT, UserKeys.STATUS_INVISIBLE ]] def unset_admin_room(self, room_uuid: str) -> None: self.redis.delete(RedisKeys.admin_room()) def set_admin_room(self, room_uuid: str) -> None: self.redis.set(RedisKeys.admin_room(), room_uuid) def create_admin_room(self) -> str: admin_room_id = self.get_admin_room() if admin_room_id is not None: return admin_room_id try: self.create_user('0', 'Admin') except UserExistsException: pass channel_id = str(uuid()) room_id = str(uuid()) self.create_channel('Admins', channel_id, '0') self.redis.set(RedisKeys.admin_room(), room_id) self.redis.hset(RedisKeys.room_name_for_id(), room_id, 'Admins') self.redis.hset(RedisKeys.rooms(channel_id), room_id, 'Admins') self.redis.hset(RedisKeys.channel_for_rooms(), room_id, channel_id) self.redis.sadd(RedisKeys.non_ephemeral_rooms(), room_id) acls = { RoleKeys.ADMIN: '', RoleKeys.SUPER_USER: '' } samechannel = { 'samechannel': '' } self.add_acls_in_channel_for_action(channel_id, ApiActions.LIST, acls) self.add_acls_in_channel_for_action(channel_id, ApiActions.JOIN, acls) self.add_acls_in_room_for_action(room_id, ApiActions.JOIN, acls) self.add_acls_in_room_for_action(room_id, ApiActions.LIST, acls) self.add_acls_in_room_for_action(room_id, ApiActions.CROSSROOM, samechannel) return room_id def get_admin_room(self) -> str: room_id = self.redis.get(RedisKeys.admin_room()) if room_id is None or len(str(room_id, 'utf-8').strip()) == 0: return None return str(room_id, 'utf-8') def get_reason_for_ban_global(self, user_id: str) -> str: return '' def get_reason_for_ban_channel(self, user_id: str, channel_uuid: str) -> str: return '' def get_reason_for_ban_room(self, user_id: str, room_uuid: str) -> str: return '' def _has_role_in_room(self, role: str, room_id: str, user_id: str) -> bool: roles = self.redis.hget(RedisKeys.room_roles(room_id), user_id) if roles is None: return False return role in str(roles, 'utf-8').split(',') def _has_role_in_channel(self, role: str, channel_id: str, user_id: str) -> bool: roles = self.redis.hget(RedisKeys.channel_roles(channel_id), user_id) if roles is None: return False return role in str(roles, 'utf-8').split(',') def _has_global_role(self, user_id: str, role: str) -> bool: roles = self.redis.hget(RedisKeys.global_roles(), user_id) if roles is None: return False return role in str(roles, 'utf-8').split(',') def _add_channel_role(self, role: str, channel_id: str, user_id: str): self.get_channel_name(channel_id) roles = self.redis.hget(RedisKeys.channel_roles(channel_id), user_id) if roles is None: roles = role else: roles = set(str(roles, 'utf-8').split(',')) roles.add(role) roles = ','.join(roles) self.redis.hset(RedisKeys.channel_roles(channel_id), user_id, roles) def _add_room_role(self, role: str, room_id: str, user_id: str): self.get_room_name(room_id) roles = self.redis.hget(RedisKeys.room_roles(room_id), user_id) if roles is None: roles = role else: roles = set(str(roles, 'utf-8').split(',')) roles.add(role) roles = ','.join(roles) self.redis.hset(RedisKeys.room_roles(room_id), user_id, roles) def _add_global_role(self, user_id: str, role: str): key = RedisKeys.global_roles() roles = self.redis.hget(key, user_id) if roles is None: roles = role else: roles = set(str(roles, 'utf-8').split(',')) roles.add(role) roles = ','.join(roles) self.redis.hset(key, user_id, roles) def _remove_channel_role(self, role: str, channel_id: str, user_id: str): roles = self.redis.hget(RedisKeys.channel_roles(channel_id), user_id) if roles is None: return roles = set(str(roles, 'utf-8').split(',')) if role not in roles: return roles.remove(role) roles = ','.join(roles) self.redis.hset(RedisKeys.channel_roles(channel_id), user_id, roles) def _remove_room_role(self, role: str, room_id: str, user_id: str): roles = self.redis.hget(RedisKeys.room_roles(room_id), user_id) if roles is None: return roles = set(str(roles, 'utf-8').split(',')) if role not in roles: return roles.remove(role) roles = ','.join(roles) self.redis.hset(RedisKeys.room_roles(room_id), user_id, roles) def _remove_global_role(self, user_id: str, role: str): key = RedisKeys.global_roles() roles = self.redis.hget(key, user_id) if roles is None: return roles = set(str(roles, 'utf-8').split(',')) if role not in roles: return roles.remove(role) roles = ','.join(roles) self.redis.hset(key, user_id, roles) def get_super_users(self) -> dict: users = self.redis.hgetall(RedisKeys.global_roles()) super_users = dict() for user_id in users.keys(): user_id = str(user_id, 'utf-8') super_users[user_id] = self.get_user_name(user_id) return super_users def set_super_user(self, user_id: str) -> None: self._add_global_role(user_id, RoleKeys.SUPER_USER) def is_super_user(self, user_id: str) -> bool: return self._has_global_role(user_id, RoleKeys.SUPER_USER) def is_admin(self, channel_id: str, user_id: str) -> bool: return self._has_role_in_channel(RoleKeys.ADMIN, channel_id, user_id) def is_moderator(self, room_id: str, user_id: str) -> bool: return self._has_role_in_room(RoleKeys.MODERATOR, room_id, user_id) def is_global_moderator(self, user_id: str) -> bool: return self._has_global_role(RoleKeys.GLOBAL_MODERATOR, user_id) def is_owner(self, room_id: str, user_id: str) -> bool: return self._has_role_in_room(RoleKeys.OWNER, room_id, user_id) def is_owner_channel(self, channel_id: str, user_id: str) -> bool: return self._has_role_in_channel(RoleKeys.OWNER, channel_id, user_id) def set_admin(self, channel_id: str, user_id: str): if not self.channel_exists(channel_id): raise NoSuchChannelException(channel_id) self._add_channel_role(RoleKeys.ADMIN, channel_id, user_id) def set_moderator(self, room_id: str, user_id: str): self._add_room_role(RoleKeys.MODERATOR, room_id, user_id) def set_global_moderator(self, room_id: str, user_id: str): self._add_global_role(user_id, RoleKeys.GLOBAL_MODERATOR) def set_owner(self, room_id: str, user_id: str): self._add_room_role(RoleKeys.OWNER, room_id, user_id) def set_owner_channel(self, channel_id: str, user_id: str): self._add_channel_role(RoleKeys.OWNER, channel_id, user_id) def remove_admin(self, channel_id: str, user_id: str) -> None: if not self.channel_exists(channel_id): raise NoSuchChannelException(channel_id) self._remove_channel_role(RoleKeys.ADMIN, channel_id, user_id) def remove_owner_channel(self, channel_id: str, user_id: str) -> None: if not self.channel_exists(channel_id): raise NoSuchChannelException(channel_id) self._remove_channel_role(RoleKeys.OWNER, channel_id, user_id) def remove_moderator(self, room_id: str, user_id: str) -> None: self.get_room_name(room_id) if not self.channel_for_room(room_id): raise NoChannelFoundException(room_id) self._remove_room_role(RoleKeys.MODERATOR, room_id, user_id) def remove_super_user(self, user_id: str) -> None: self._remove_global_role(user_id, RoleKeys.SUPER_USER) def remove_global_moderator(self, user_id: str) -> None: self._remove_global_role(user_id, RoleKeys.GLOBAL_MODERATOR) def remove_owner(self, room_id: str, user_id: str) -> None: if not self.channel_for_room(room_id): raise NoSuchRoomException(room_id) self._remove_room_role(RoleKeys.OWNER, room_id, user_id) def get_channels(self) -> dict: all_channels = self.redis.hgetall(RedisKeys.channels()) clean = dict() for channel_id, channel_name in all_channels.items(): # second argument in tuple is sort order, but it's not supported with redis db clean[str(channel_id, 'utf-8')] = (str(channel_name, 'utf-8'), 1, 'normal') return clean def update_room_sort_order(self, room_uuid: str, sort_order: int) -> None: # not supported in redis db pass def update_channel_sort_order(self, channel_uuid: str, sort: int) -> None: # not supported in redis db pass def rooms_for_channel_without_info(self, channel_id: str) -> dict: rooms = self.rooms_for_channel(channel_id) return { room_id: { 'name': room['name'], 'ephemeral': room['ephemeral'] } for room_id, room in rooms.items() } def rooms_for_channel(self, channel_id) -> dict: all_rooms = self.redis.hgetall(RedisKeys.rooms(channel_id)) clean = dict() for room_id, room_name in all_rooms.items(): if room_name is None: raise NoRoomNameException(room_id) room_id = str(room_id, 'utf-8') clean[room_id] = { 'name': str(room_name, 'utf-8'), 'sort_order': 1, 'ephemeral': self.is_room_ephemeral(room_id), 'users': len(self.users_in_room(room_id)) } return clean def room_exists(self, channel_id: str, room_id: str) -> bool: return self.redis.hexists(RedisKeys.rooms(channel_id), room_id) def get_room_id_for_name(self, room_name: str) -> str: raise NotImplementedError('redis db does not support getting room id from name') def room_name_exists(self, channel_id, room_name: str) -> bool: cleaned = set() for existing_room_name in self.redis.hvals(RedisKeys.rooms(channel_id)): cleaned.add(str(existing_room_name, 'utf-8').lower()) if type(room_name) == bytes: room_name = str(room_name, 'utf-8') return room_name.lower() in cleaned def channel_name_exists(self, channel_name: str) -> bool: cleaned = set() for candidate in self.redis.hvals(RedisKeys.channels()): cleaned.add(str(candidate, 'utf-8').lower()) if type(channel_name) == bytes: channel_name = str(channel_name, 'utf-8') return channel_name.lower() in cleaned def channel_exists(self, channel_id) -> bool: if channel_id is None or channel_id == '': return False return self.redis.hexists(RedisKeys.channels(), channel_id) def create_user(self, user_id: str, user_name: str) -> None: if user_name is None or len(user_name.strip()) == 0: raise EmptyUserNameException(user_id) if user_id is None or len(user_id.strip()) == 0: raise EmptyUserIdException() try: self.get_user_name(user_id) raise UserExistsException(user_id) except NoSuchUserException: pass key = RedisKeys.auth_key(user_id) self.redis.hset(key, SessionKeys.user_id.value, user_id) self.redis.hset(key, SessionKeys.user_name.value, user_name) self.redis.hset(RedisKeys.user_names(), user_id, user_name) def get_avatars_for(self, user_ids: set) -> dict: return dict() def room_contains(self, room_id: str, user_id: str) -> bool: self.get_room_name(room_id) self.channel_for_room(room_id) return self.redis.hexists(RedisKeys.users_in_room(room_id), user_id) def users_in_room(self, room_id: str, this_user_id: str=None, skip_cache: bool=False) -> dict: try: self.get_room_name(room_id) except NoSuchRoomException: return dict() self.channel_for_room(room_id) users = self.redis.hgetall(RedisKeys.users_in_room(room_id)) cleaned_users = dict() for user_id, user_name in users.items(): user_id = str(user_id, 'utf-8') cleaned_users[user_id] = str(user_name, 'utf-8') return cleaned_users def leave_room(self, user_id: str, room_id: str) -> None: self.get_room_name(room_id) self.redis.hdel(RedisKeys.users_in_room(room_id), user_id) self.redis.hdel(RedisKeys.rooms_for_user(user_id), room_id) def delete_acl_in_room_for_action(self, room_id: str, acl_type: str, action: str) -> None: self.get_room_name(room_id) self.channel_for_room(room_id) if action not in ApiActions.all_api_actions: raise InvalidApiActionException(action) key = '%s|%s' % (action, acl_type) self.redis.hdel(RedisKeys.room_acl(room_id), key) def add_default_room(self, room_id: str) -> None: self.redis.sadd(RedisKeys.default_rooms(), room_id) def remove_default_room(self, room_id: str) -> None: self.redis.srem(RedisKeys.default_rooms(), room_id) def get_default_rooms(self) -> list: self.redis.smembers(RedisKeys.default_rooms()) def delete_acl_in_channel_for_action(self, channel_id: str, acl_type: str, action: str) -> None: if not self.channel_exists(channel_id): raise NoSuchChannelException(channel_id) if action not in ApiActions.all_api_actions: raise InvalidApiActionException(action) key = '%s|%s' % (action, acl_type) self.redis.hdel(RedisKeys.channel_acl(channel_id), key) def get_temp_rooms_user_is_owner_for(self, user_id: str) -> None: return list() def update_acl_in_room_for_action(self, channel_id: str, room_id: str, action: str, acl_type: str, acl_value: str) -> None: if not self.channel_exists(channel_id): raise NoSuchChannelException(channel_id) if not self.room_exists(channel_id, room_id): raise NoSuchRoomException(room_id) self.add_acls_in_room_for_action(room_id, action, {acl_type: acl_value}) def update_acl_in_channel_for_action(self, channel_id: str, action: str, acl_type: str, acl_value: str) -> None: self.add_acls_in_channel_for_action(channel_id, action, {acl_type: acl_value}) def add_acls_in_channel_for_action(self, channel_id: str, action: str, acls: dict) -> None: if not self.channel_exists(channel_id): raise NoSuchChannelException(channel_id) if action not in ApiActions.all_api_actions: raise InvalidApiActionException(action) new_acls = dict() acl_configs = environ.env.config.get(ConfigKeys.ACL) for acl_type, acl_value in acls.items(): if acl_type not in acl_configs['available']['acls']: raise InvalidAclTypeException(acl_type) acl_configs['validation'][acl_type]['value'].validate_new_acl(acl_value) key = '%s|%s' % (action, acl_type) new_acls[key] = acl_value if len(new_acls) == 0: return self.redis.hmset(RedisKeys.channel_acl(channel_id), new_acls) def add_acls_in_room_for_action(self, room_id: str, action: str, acls: dict) -> None: if self.channel_for_room(room_id) is None: raise NoSuchRoomException(room_id) if action not in ApiActions.all_api_actions: raise InvalidApiActionException(action) acl_configs = environ.env.config.get(ConfigKeys.ACL) new_acls = dict() for acl_type, acl_value in acls.items(): if acl_type not in acl_configs['available']['acls']: raise InvalidAclTypeException(acl_type) acl_configs['validation'][acl_type]['value'].validate_new_acl(acl_value) key = '%s|%s' % (action, acl_type) new_acls[key] = acl_value if len(new_acls) == 0: return self.redis.hmset(RedisKeys.room_acl(room_id), new_acls) def _is_banned(self, ban): time = None if ban is not None: ban = str(ban, 'utf-8') time = ban.split('|', 2)[1] return time is not None, time def is_banned_globally(self, user_id: str) -> (bool, Union[str, None]): ban = self.redis.hget(RedisKeys.banned_users(), user_id) is_banned, time = self._is_banned(ban) if not is_banned: return False, None now = datetime.utcnow() end = datetime.fromtimestamp(float(time)) if now > end: self.redis.hdel(RedisKeys.banned_users(), user_id) return False, None return True, time def is_banned_from_channel(self, channel_id: str, user_id: str) -> (bool, Union[str, None]): ban = self.redis.hget(RedisKeys.banned_users_channel(channel_id), user_id) is_banned, time = self._is_banned(ban) if not is_banned: return False, None now = datetime.utcnow() end = datetime.fromtimestamp(float(time)) if now > end: self.redis.hdel(RedisKeys.banned_users(), user_id) return False, None return True, time def is_banned_from_room(self, room_id: str, user_id: str) -> (bool, Union[str, None]): ban = self.redis.hget(RedisKeys.banned_users(room_id), user_id) is_banned, time = self._is_banned(ban) if not is_banned: return False, None now = datetime.utcnow() end = datetime.fromtimestamp(float(time)) if now > end: self.redis.hdel(RedisKeys.banned_users(), user_id) return False, None return True, time def remove_global_ban(self, user_id: str) -> str: self.redis.hdel(RedisKeys.banned_users(), user_id) def remove_channel_ban(self, channel_id: str, user_id: str) -> str: self.redis.hdel(RedisKeys.banned_users_channel(channel_id), user_id) def remove_channel(self, channel_id: str) -> None: self.redis.hdel(RedisKeys.channels(), channel_id) self.redis.hdel(RedisKeys.banned_users_channel(channel_id)) self.redis.hdel(RedisKeys.channel_acl(channel_id)) def remove_room_ban(self, room_id: str, user_id: str) -> str: self.redis.hdel(RedisKeys.banned_users(room_id), user_id) def get_bans_for_user(self, user_id: str, session=None) -> dict: def _to_date(_timestamp): return datetime.fromtimestamp(int(_timestamp)).strftime(ConfigKeys.DEFAULT_DATE_FORMAT) now = datetime.utcnow() all_channels = self.redis.hgetall(RedisKeys.channels()) channel_ids = list() room_ids = list() output = { 'global': dict(), 'room': dict(), 'channel': dict() } for channel_id, _ in all_channels.items(): channel_ids.append(str(channel_id, 'utf-8')) for channel_id in channel_ids: all_rooms = self.redis.hgetall(RedisKeys.rooms(channel_id)) for room_id, _ in all_rooms.items(): room_ids.append(str(room_id, 'utf-8')) for channel_id in channel_ids: r_key = RedisKeys.banned_users_channel(channel_id) if not self.redis.hexists(r_key, user_id): continue ban_info = self.redis.hget(RedisKeys, r_key) ban_duration, ban_timestamp, _ = ban_info.split('|', 2) if datetime.fromtimestamp(int(ban_timestamp)) < now: self.redis.hdel(r_key, user_id) continue output['channel'][channel_id] = { 'name': b64e(self.get_channel_name(channel_id)), 'duration': ban_duration, 'timestamp': _to_date(ban_timestamp) } for room_id in room_ids: r_key = RedisKeys.banned_users(room_id) if not self.redis.hexists(r_key, user_id): continue ban_info = self.redis.hget(RedisKeys, r_key) ban_duration, ban_timestamp, _ = ban_info.split('|', 2) if datetime.fromtimestamp(int(ban_timestamp)) < now: self.redis.hdel(r_key, user_id) continue output['room'][room_id] = { 'name': b64e(self.get_room_name(room_id)), 'duration': ban_duration, 'timestamp': _to_date(ban_timestamp) } r_key = RedisKeys.banned_users() if self.redis.hexists(r_key, user_id): ban_info = self.redis.hget(RedisKeys.banned_users(), user_id) ban_duration, ban_timestamp, _ = ban_info.split('|', 2) if datetime.fromtimestamp(int(ban_timestamp)) < now: self.redis.hdel(r_key, user_id) else: output['global'] = { 'duration': ban_duration, 'timestamp': _to_date(ban_timestamp) } return output def get_user_ban_status(self, room_id: str, user_id: str) -> dict: channel_id = self.channel_for_room(room_id) global_ban = self.redis.hget(RedisKeys.banned_users(), user_id) channel_ban = self.redis.hget(RedisKeys.banned_users_channel(channel_id), user_id) room_ban = self.redis.hget(RedisKeys.banned_users(room_id), user_id) global_timestamp = '' channel_timestamp = '' room_timestamp = '' if global_ban is not None: global_ban = str(global_ban, 'utf-8') global_timestamp = global_ban.split('|', 2)[1] if channel_ban is not None: channel_ban = str(channel_ban, 'utf-8') channel_timestamp = channel_ban.split('|', 2)[1] if room_ban is not None: room_ban = str(room_ban, 'utf-8') room_timestamp = room_ban.split('|', 2)[1] return { 'global': global_timestamp, 'channel': channel_timestamp, 'room': room_timestamp } def get_banned_users_global(self) -> dict: now = datetime.utcnow() output = dict() def for_global_ban(_user_id, _ban_info): ban_duration, ban_timestamp, username = _ban_info.split('|', 2) if datetime.fromtimestamp(int(ban_timestamp)) < now: self.redis.hdel(RedisKeys.banned_users(), _user_id) return output[_user_id] = { 'name': b64e(self.get_user_name(_user_id)), 'duration': ban_duration, 'timestamp': datetime.fromtimestamp(int(ban_timestamp)).strftime(ConfigKeys.DEFAULT_DATE_FORMAT) } global_bans = self.redis.hgetall(RedisKeys.banned_users()) for user_id, ban_info in global_bans.items(): user_id = str(user_id, 'utf-8') ban_info = str(ban_info, 'utf-8') for_global_ban(user_id, ban_info) return output # TODO: use @lru_cache? def get_banned_users_for_channel(self, channel_id): now = datetime.utcnow() output = dict() def for_local_ban_channel(_user_id, _channel_id, _ban_info): ban_duration, ban_timestamp, username = _ban_info.split('|', 2) if datetime.fromtimestamp(int(ban_timestamp)) < now: self.redis.hdel(RedisKeys.banned_users_channel(_channel_id), _user_id) return output[_user_id] = { 'name': b64e(self.get_user_name(_user_id)), 'duration': ban_duration, 'timestamp': datetime.fromtimestamp(int(ban_timestamp)).strftime(ConfigKeys.DEFAULT_DATE_FORMAT) } channel_bans = self.redis.hgetall(RedisKeys.banned_users_channel(channel_id)) for user_id, ban_info in channel_bans.items(): user_id = str(user_id, 'utf-8') ban_info = str(ban_info, 'utf-8') for_local_ban_channel(user_id, channel_id, ban_info) return output # TODO: use @lru_cache? def get_banned_users_for_room(self, room_id) -> dict: now = datetime.utcnow() output = dict() def for_local_ban(_user_id, _room_id, _ban_info): ban_duration, ban_timestamp, username = _ban_info.split('|', 2) if datetime.fromtimestamp(int(ban_timestamp)) < now: self.redis.hdel(RedisKeys.banned_users(_room_id), _user_id) return output[_user_id] = { 'name': b64e(self.get_user_name(_user_id)), 'duration': ban_duration, 'timestamp': datetime.fromtimestamp(int(ban_timestamp)).strftime(ConfigKeys.DEFAULT_DATE_FORMAT) } local_bans = self.redis.hgetall(RedisKeys.banned_users(room_id)) for user_id, ban_info in local_bans.items(): user_id = str(user_id, 'utf-8') ban_info = str(ban_info, 'utf-8') for_local_ban(user_id, room_id, ban_info) return output def get_banned_users(self) -> dict: all_channels = self.redis.hgetall(RedisKeys.channels()) def get_banned_users_all_channels() -> dict: output = dict() for channel_id, _ in all_channels.items(): channel_id = str(channel_id, 'utf-8') bans = { 'name': b64e(self.get_channel_name(channel_id)), 'users': self.get_banned_users_for_channel(channel_id) } if len(bans['users']) > 0: output[channel_id] = bans return output def get_banned_users_all_rooms() -> dict: output = dict() for channel_id, _ in all_channels.items(): channel_id = str(channel_id, 'utf-8') rooms_for_channel = self.redis.hgetall(RedisKeys.rooms(channel_id)) for room_id, _ in rooms_for_channel.items(): room_id = str(room_id, 'utf-8') bans = { 'name': b64e(self.get_room_name(room_id)), 'users': self.get_banned_users_for_room(room_id) } if len(bans['users']) > 0: output[room_id] = bans return output return { 'global': self.get_banned_users_global(), 'channels': get_banned_users_all_channels(), 'rooms': get_banned_users_all_rooms() } def kick_user(self, room_id: str, user_id: str) -> None: self.leave_room(user_id, room_id) def _get_ban_timestamp(self, ban) -> (str, str, str): if ban is None: return None, None, None ban = str(ban, 'utf-8') return ban.split('|', 2) def get_global_ban_timestamp(self, user_id: str) -> (str, str, str): ban = self.redis.hget(RedisKeys.banned_users(), user_id) return self._get_ban_timestamp(ban) def get_channel_ban_timestamp(self, channel_id: str, user_id: str) -> (str, str, str): ban = self.redis.hget(RedisKeys.banned_users_channel(channel_id), user_id) return self._get_ban_timestamp(ban) def get_room_ban_timestamp(self, room_id: str, user_id: str) -> (str, str, str): ban = self.redis.hget(RedisKeys.banned_users(room_id), user_id) return self._get_ban_timestamp(ban) def ban_user_global(self, user_id: str, ban_timestamp: str, ban_duration: str, reason: str=None, banner_id: str=None): user_name = '' try: user_name = self.get_user_name(user_id) except NoSuchUserException: pass self.redis.hset(RedisKeys.banned_users(), user_id, '%s|%s|%s' % (ban_duration, ban_timestamp, user_name)) def ban_user_room(self, user_id: str, ban_timestamp: str, ban_duration: str, room_id: str, reason: str=None, banner_id: str=None): try: self.channel_for_room(room_id) except NoChannelFoundException: raise NoSuchRoomException(room_id) user_name = '' try: user_name = self.get_user_name(user_id) except NoSuchUserException: pass self.redis.hset(RedisKeys.banned_users(room_id), user_id, '%s|%s|%s' % (ban_duration, ban_timestamp, user_name)) def ban_user_channel(self, user_id: str, ban_timestamp: str, ban_duration: str, channel_id: str, reason: str=None, banner_id: str=None): user_name = '' try: user_name = self.get_user_name(user_id) except NoSuchUserException: pass self.redis.hset(RedisKeys.banned_users_channel(channel_id), user_id, '%s|%s|%s' % (ban_duration, ban_timestamp, user_name)) def get_acls_in_room_for_action(self, room_id: str, action: str) -> dict: self.get_room_name(room_id) self.channel_for_room(room_id) acls = self.redis.hgetall(RedisKeys.room_acl(room_id)) acls_cleaned = dict() for acl_key, acl_value in acls.items(): acl_action, acl_type = str(acl_key, 'utf-8').split('|', 1) if acl_action != action: continue acls_cleaned[acl_type] = str(acl_value, 'utf-8') return acls_cleaned def get_acls_in_channel_for_action(self, channel_id: str, action: str) -> dict: self.get_channel_name(channel_id) acls = self.redis.hgetall(RedisKeys.channel_acl(channel_id)) acls_cleaned = dict() for acl_key, acl_value in acls.items(): acl_action, acl_type = str(acl_key, 'utf-8').split('|', 1) if acl_action != action: continue acls_cleaned[acl_type] = str(acl_value, 'utf-8') return acls_cleaned def get_acl_validation_value(self, acl_type: str, validation_method: str) -> str: if acl_type is None or len(acl_type.strip()) == 0: raise InvalidAclTypeException(acl_type) if validation_method is None or len(validation_method.strip()) == 0: raise InvalidAclValueException(acl_type, validation_method) value = environ.env.redis.hget(RedisKeys.acl_validations(acl_type), validation_method) if value is None: raise AclValueNotFoundException(acl_type, validation_method) value = str(value, 'utf-8') if len(value.strip()) == 0: raise AclValueNotFoundException(acl_type, validation_method) return value def get_all_acls_channel(self, channel_id: str) -> dict: self.get_channel_name(channel_id) acls = self.redis.hgetall(RedisKeys.channel_acl(channel_id)) acls_cleaned = dict() for acl_key, acl_value in acls.items(): acl_action, acl_type = str(acl_key, 'utf-8').split('|', 1) if acl_action not in acls_cleaned: acls_cleaned[acl_action] = dict() acls_cleaned[acl_action][acl_type] = str(acl_value, 'utf-8') return acls_cleaned def get_all_acls_room(self, room_id: str) -> dict: self.get_room_name(room_id) self.channel_for_room(room_id) acls = self.redis.hgetall(RedisKeys.room_acl(room_id)) acls_cleaned = dict() for acl_key, acl_value in acls.items(): acl_action, acl_type = str(acl_key, 'utf-8').split('|', 1) if acl_action not in acls_cleaned: acls_cleaned[acl_action] = dict() acls_cleaned[acl_action][acl_type] = str(acl_value, 'utf-8') return acls_cleaned def channel_for_room(self, room_id: str) -> str: if room_id is None or len(room_id.strip()) == 0: raise NoSuchRoomException(room_id) value = self.env.cache.get_channel_for_room(room_id) if value is not None: return value self.get_room_name(room_id) channel_id = self.redis.hget(RedisKeys.channel_for_rooms(), room_id) if channel_id is None: raise NoChannelFoundException(room_id) channel_id = str(channel_id, 'utf-8') self.env.cache.set_channel_for_room(channel_id, room_id) return channel_id def set_user_name(self, user_id: str, user_name: str) -> None: self.redis.hset(RedisKeys.auth_key(user_id), SessionKeys.user_name.value, user_name) self.redis.hset(RedisKeys.user_names(), user_id, user_name) def get_user_name(self, user_id: str) -> str: key = RedisKeys.user_names() name = self.redis.hget(key, user_id) if name is None: raise NoSuchUserException(user_id) return str(name, 'utf-8') def _get_users_with_role(self, roles: dict, role_key: str): if roles is None or len(roles) == 0: return dict() cleaned = dict() for user_id, user_roles in roles.items(): user_id = str(user_id, 'utf-8') user_roles = str(user_roles, 'utf-8') if role_key not in user_roles.split(','): continue try: cleaned[user_id] = self.get_user_name(user_id) except NoSuchUserException: logger.error('no username found for user_id %s' % user_id) return cleaned def add_sid_for_user(self, user_id: str, sid: str) -> None: if user_id is None or len(user_id.strip()) == 0: raise EmptyUserIdException(user_id) self.redis.hset(RedisKeys.sid_for_user_id(), user_id, sid) def reset_sids_for_user(self, user_id: str) -> None: if user_id is None or len(user_id.strip()) == 0: raise EmptyUserIdException(user_id) self.redis.hdel(RedisKeys.sid_for_user_id(), user_id) def remove_sid_for_user(self, user_id: str, sid: str) -> None: self.reset_sids_for_user(user_id) def get_sids_for_user(self, user_id: str) -> list: if user_id is None or len(user_id.strip()) == 0: raise EmptyUserIdException(user_id) sid = self.redis.hget(RedisKeys.sid_for_user_id(), user_id) if sid is None: return list() return [str(sid, 'utf-8')] def _get_users_with_role_in_channel(self, channel_id: str, role_key: str): roles = self.redis.hgetall(RedisKeys.channel_roles(channel_id)) return self._get_users_with_role(roles, role_key) def _get_users_with_role_in_room(self, room_id: str, role_key: str): roles = self.redis.hgetall(RedisKeys.room_roles(room_id)) return self._get_users_with_role(roles, role_key) def get_admins_channel(self, channel_id: str) -> dict: self.get_channel_name(channel_id) return self._get_users_with_role_in_channel(channel_id, RoleKeys.ADMIN) def get_owners_channel(self, channel_id: str) -> dict: self.get_channel_name(channel_id) return self._get_users_with_role_in_channel(channel_id, RoleKeys.OWNER) def get_owners_room(self, room_id: str) -> dict: self.get_room_name(room_id) return self._get_users_with_role_in_room(room_id, RoleKeys.OWNER) def get_moderators_room(self, room_id: str) -> dict: self.get_room_name(room_id) return self._get_users_with_role_in_room(room_id, RoleKeys.MODERATOR) def remove_current_rooms_for_user(self, user_id: str) -> None: self.redis.delete(RedisKeys.rooms_for_user(user_id)) def get_room_name(self, room_id: str) -> str: room_name = self.redis.hget(RedisKeys.room_name_for_id(), room_id) if room_name is None: raise NoSuchRoomException(room_id) return room_name.decode('utf-8') def get_user_infos(self, user_ids: set) -> dict: user_infos = dict() for user_id in user_ids: user_info = self.env.auth.get_user_info(user_id) user_infos[user_id] = user_info return user_infos def set_user_info(self, user_id: str, user_info: dict) -> None: for key, value in user_info.items(): self.env.auth.update_session_for_key(user_id, key, value) def get_channel_name(self, channel_id: str) -> str: channel_name = self.env.cache.get_channel_name(channel_id) if channel_name is not None: return channel_name channel_name = self.redis.hget(RedisKeys.channels(), channel_id) if channel_name is None: raise NoSuchChannelException(channel_id) channel_name = str(channel_name, 'utf-8') self.env.cache.set_channel_name(channel_id, channel_name) return channel_name def rooms_for_user(self, user_id: str, skip_cache: bool = False) -> dict: clean_rooms = dict() rooms = self.redis.hgetall(RedisKeys.rooms_for_user(user_id)) for room_id, room_name in rooms.items(): room_id, room_name = str(room_id, 'utf-8'), str(room_name, 'utf-8') clean_rooms[room_id] = room_name return clean_rooms def join_room(self, user_id: str, user_name: str, room_id: str, room_name: str) -> None: self.redis.hset(RedisKeys.rooms_for_user(user_id), room_id, room_name) self.redis.hset(RedisKeys.users_in_room(room_id), user_id, user_name) def create_channel(self, channel_name, channel_id, user_id) -> None: if self.channel_exists(channel_id): raise ChannelExistsException(channel_id) if channel_name is None or len(channel_name.strip()) == 0: raise EmptyChannelNameException(channel_id) if self.channel_name_exists(channel_name): raise ChannelNameExistsException(channel_name) self.env.cache.set_channel_exists(channel_id) self.redis.hset(RedisKeys.channels(), channel_id, channel_name) self.set_owner_channel(channel_id, user_id) def create_room(self, room_name: str, room_id: str, channel_id: str, user_id: str, user_name: str, ephemeral: bool=True, sort_order: int=False) -> None: if self.env.cache.get_channel_exists(channel_id) is None: if not self.channel_exists(channel_id): raise NoSuchChannelException(channel_id) if room_name is None or len(room_name.strip()) == 0: raise EmptyRoomNameException(room_id) if self.room_exists(channel_id, room_id): raise RoomExistsException(room_id) if self.room_name_exists(channel_id, room_name): raise RoomNameExistsForChannelException(channel_id, room_name) if ephemeral: self.redis.sadd(RedisKeys.non_ephemeral_rooms(), room_id) self.redis.hset(RedisKeys.room_name_for_id(), room_id, room_name) self.redis.hset(RedisKeys.rooms(channel_id), room_id, room_name) self.redis.hset(RedisKeys.channel_for_rooms(), room_id, channel_id) self.redis.hset(RedisKeys.user_names(), user_id, user_name) self.set_owner(room_id, user_id) def rename_channel(self, channel_id: str, channel_name: str) -> None: self.get_channel_name(channel_id) if self.channel_name_exists(channel_name): raise ChannelNameExistsException(channel_name) if channel_name is None or len(channel_name.strip()) == 0: raise EmptyChannelNameException(channel_id) self.redis.hset(RedisKeys.channels(), channel_id, channel_name) self.env.cache.set_channel_name(channel_id, channel_name) def rename_room(self, channel_id: str, room_id: str, room_name: str) -> None: if self.env.cache.get_channel_exists(channel_id) is None: if not self.channel_exists(channel_id): raise NoSuchChannelException(channel_id) if room_name is None or len(room_name.strip()) == 0: raise EmptyRoomNameException(channel_id) if not self.room_exists(channel_id, room_id): raise NoSuchRoomException(room_id) if self.room_name_exists(channel_id, room_name): raise RoomNameExistsForChannelException(channel_id, room_name) self.redis.hset(RedisKeys.room_name_for_id(), room_id, room_name) self.redis.hset(RedisKeys.rooms(channel_id), room_id, room_name) def type_of_rooms_in_channel(self, channel_id: str) -> str: return 'mix' def remove_room(self, channel_id: str, room_id: str) -> None: if self.env.cache.get_channel_exists(channel_id) is None: if not self.channel_exists(channel_id): raise NoSuchChannelException(channel_id) if not self.room_exists(channel_id, room_id): raise NoSuchRoomException(room_id) self.redis.srem(RedisKeys.non_ephemeral_rooms(), room_id) self.redis.hdel(RedisKeys.room_name_for_id(), room_id) self.redis.delete(RedisKeys.room_roles(room_id)) self.redis.hdel(RedisKeys.rooms(channel_id), room_id) self.redis.hdel(RedisKeys.channel_for_rooms(), room_id) def get_user_status(self, user_id: str, skip_cache: bool = False) -> str: status = self.env.cache.get_user_status(user_id) if status is not None: return status status = self.redis.get(RedisKeys.user_status(user_id)) if status is None: return UserKeys.STATUS_UNAVAILABLE return str(status, 'utf-8') def set_user_offline(self, user_id: str) -> None: self.env.cache.set_user_offline(user_id) def set_user_online(self, user_id: str) -> None: self.env.cache.set_user_online(user_id) self.redis.set(RedisKeys.user_status(user_id), UserKeys.STATUS_AVAILABLE) def set_user_invisible(self, user_id: str, is_offline=False) -> None: if is_offline: self.env.cache.setUser_status_invisible(user_id) else: self.env.cache.set_user_invisible(user_id) def update_last_read_for(self, users: set, room_id: str, time_stamp: int) -> None: self.get_room_name(room_id) redis_key = RedisKeys.last_read(room_id) for user_id in users: self.redis.hset(redis_key, user_id, time_stamp) def get_last_read_timestamp(self, room_id: str, user_id: str) -> int: timestamp = self.redis.hget(RedisKeys.last_read(room_id), user_id) if timestamp is None: return None return int(str(timestamp, 'utf-8'))