dino/validation/request.py
import sys
from activitystreams.models.activity import Activity
from activitystreams.models.defobject import DefObject
from activitystreams.models.actor import Actor
from activitystreams.models.target import Target
import logging
import traceback
from uuid import UUID
from dino import utils
from dino.config import SessionKeys
from dino.config import ApiActions
from dino.config import ApiTargets
from dino.config import ConfigKeys
from dino.config import ErrorCodes as ECodes
from dino.validation.base import BaseValidator
from dino.exceptions import NoSuchRoomException
from dino.exceptions import NoSuchUserException
from dino.exceptions import NoSuchChannelException
from dino.exceptions import NoChannelFoundException
from dino.exceptions import MultipleRoomsFoundForNameException
from dino import validation
from dino import environ
from dino.validation.duration import DurationValidator
__author__ = 'Oscar Eriksson <oscar.eriks@gmail.com>'
logger = logging.getLogger(__name__)
class RequestValidator(BaseValidator):
def on_msg_status(self, _: Activity) -> (bool, int, str):
return True, None, None
def on_message(self, activity: Activity) -> (bool, int, str):
room_id = activity.target.id
user_id = activity.actor.id
object_type = activity.target.object_type
message = activity.object.content
from_room_id = None
if hasattr(activity.actor, 'url'):
from_room_id = activity.actor.url
if message is None or len(message.strip()) == 0:
return False, ECodes.EMPTY_MESSAGE, 'empty message body'
if not utils.is_base64(message):
return False, ECodes.NOT_BASE64, 'invalid message content, not base64 encoded'
if room_id is None or room_id == '':
return False, ECodes.MISSING_TARGET_ID, 'no room id specified when sending message'
if object_type not in ['room', 'private']:
return False, ECodes.INVALID_TARGET_TYPE, \
'invalid object_type "%s", must be one of [room, private]' % object_type
if object_type == 'room':
channel_id = None
if hasattr(activity, 'object') and hasattr(activity.object, 'url'):
channel_id = activity.object.url
if channel_id is None or len(channel_id.strip()) == 0:
channel_id = utils.get_channel_for_room(room_id)
if channel_id is None or channel_id == '':
return False, ECodes.MISSING_OBJECT_URL, 'no channel id specified when sending message'
activity.object.url = channel_id
activity.object.display_name = utils.get_channel_name(channel_id)
if not utils.channel_exists(channel_id):
return False, ECodes.NO_SUCH_CHANNEL, 'channel %s does not exists' % channel_id
if not utils.room_exists(channel_id, room_id):
return False, ECodes.NO_SUCH_ROOM, 'target room %s does not exist' % room_id
if from_room_id is not None:
if from_room_id != room_id and not utils.room_exists(channel_id, from_room_id):
return False, ECodes.NO_SUCH_ROOM, 'origin room %s does not exist' % from_room_id
if not utils.is_user_in_room(user_id, room_id):
logger.warning('user "%s" is not in room "%s' % (user_id, room_id))
if from_room_id is None:
return False, ECodes.USER_NOT_IN_ROOM, 'user is not in target room'
if not utils.is_user_in_room(user_id, from_room_id):
return False, ECodes.USER_NOT_IN_ROOM, 'user is not in origin room, cannot send message from there'
if not utils.can_send_cross_room(activity, from_room_id, room_id):
return False, ECodes.NOT_ALLOWED, \
'user not allowed to send cross-room msg from %s to %s' % (from_room_id, room_id)
if utils.should_validate_mutes():
is_muted, info_dict = utils.is_muted(user_id, room_id)
if is_muted:
seconds_left = info_dict['seconds']
target_id = info_dict['id']
target_name = utils.get_room_name(target_id)
json_act = utils.activity_for_already_muted(seconds_left, target_id, target_name)
return False, ECodes.USER_IS_BANNED, json_act
if utils.should_validate_whispers():
message = utils.parse_message(message)
if message is not None and utils.is_whisper(message):
users = utils.get_whisper_users_from_message(message)
if len(users) > 0:
if not utils.can_send_whisper_in_channel(activity, channel_id):
return False, ECodes.NOT_ALLOWED_TO_WHISPER_CHANNEL, 'not allowed to whisper in channel'
can_whisper, reason_code = utils.can_send_whisper_to_user(activity, message, users)
if not can_whisper:
return False, reason_code, 'not allowed to whisper this user'
else:
return False, ECodes.NO_SUCH_USER, 'no such user'
elif object_type == 'private':
channel_id = None
if hasattr(activity, 'object') and hasattr(activity.object, 'url'):
channel_id = activity.object.url
if channel_id is None or len(channel_id.strip()) == 0:
try:
channel_id = utils.get_channel_for_room(room_id)
except NoSuchRoomException:
# TODO: ignore for now, but capture so we can track; a user room won't exist, try to emit anyway
environ.env.capture_exception(sys.exc_info())
return True, False, False
if not utils.channel_exists(channel_id):
return False, ECodes.NO_SUCH_CHANNEL, 'channel %s does not exists' % channel_id
if not utils.room_exists(channel_id, room_id):
return False, ECodes.NO_SUCH_ROOM, 'target room %s does not exist' % room_id
return True, None, None
def _on_read_or_receive(self, activity: Activity, expected_verb: str) -> (bool, int, str):
if not hasattr(activity, 'verb'):
return False, ECodes.MISSING_VERB, 'no verb on activity'
if not hasattr(activity, 'target') or not hasattr(activity.target, 'id') or len(activity.target.id.strip()) == 0:
return False, ECodes.MISSING_TARGET_ID, 'no target.id on activity'
if activity.verb != expected_verb:
return False, ECodes.INVALID_VERB, 'expecting verb "%s" but was "%s"' % (expected_verb, str(activity.verb))
if not hasattr(activity, 'object') or not hasattr(activity.object, 'attachments'):
return False, ECodes.MISSING_OBJECT_ATTACHMENTS, 'no attachments found on object'
message_ids = set()
for attachment in activity.object.attachments:
message_id = attachment.id
try:
UUID(message_id)
message_ids.add(message_id.strip())
except ValueError:
return False, ECodes.VALIDATION_ERROR, \
'"%s" is not a valid uuid (activity.object.attachments.id)' % str(message_id)
if len(message_ids) == 0:
return False, ECodes.MISSING_OBJECT_ATTACHMENTS, 'no attachments found on object'
if len(message_ids) > 500:
return False, ECodes.TOO_MANY_ATTACHMENTS, \
'max 500 attachments allowed at one time, received %s' % len(message_ids)
return True, None, None
def on_read(self, activity: Activity) -> (bool, int, str):
return self._on_read_or_receive(activity, 'read')
def on_received(self, activity: Activity) -> (bool, int, str):
return self._on_read_or_receive(activity, 'receive')
def on_delete(self, activity: Activity) -> (bool, int, str):
user_id = activity.actor.id
room_id = activity.target.id
message_id = activity.object.id
if message_id is None or len(message_id.strip()) == 0:
return False, ECodes.MISSING_OBJECT_ID, 'no object ID when deleting message'
sender_can_delete = environ.env.config.get(ConfigKeys.SENDER_CAN_DELETE, False)
if sender_can_delete and utils.get_sender_for_message(message_id) == user_id:
return True, None, None
if not utils.user_is_allowed_to_delete_message(room_id, user_id):
return False, ECodes.NOT_ALLOWED, 'not allowed to remove message in room %s' % room_id
return True, None, None
def on_login(self, activity: Activity) -> (bool, int, str):
user_id = activity.actor.id
is_banned, duration = utils.is_banned_globally(user_id)
if is_banned:
environ.env.join_room(user_id)
reason = utils.reason_for_ban(user_id)
json_act = utils.activity_for_already_banned(duration, reason)
environ.env.emit(
'gn_banned', json_act, json=True, room=user_id, broadcast=False, include_self=True, namespace='/ws')
logger.info('user %s is banned from chatting for: %ss' % (user_id, duration))
return False, ECodes.USER_IS_BANNED, 'user %s is banned from chatting for: %ss' % (user_id, duration)
if hasattr(activity.actor, 'attachments') and activity.actor.attachments is not None:
for attachment in activity.actor.attachments:
environ.env.session[attachment.object_type] = attachment.content
if SessionKeys.token.value not in environ.env.session:
logger.warning('no token in session when logging in for user id %s' % str(user_id))
return False, ECodes.NO_USER_IN_SESSION, 'no token in session'
token = environ.env.session.get(SessionKeys.token.value)
is_valid, error_msg, session = self.validate_login(user_id, token)
if not is_valid:
logger.warning('login is not valid for user id %s: %s' % (str(user_id), str(error_msg)))
environ.env.stats.incr('on_login.failed')
return False, ECodes.NOT_ALLOWED, error_msg
for session_key, session_value in session.items():
environ.env.session[session_key] = session_value
return True, None, None
def _remove_or_rename_room(self, activity: Activity, action: str) -> (bool, int, str):
user_id = activity.actor.id
room_id = activity.target.id
if utils.is_owner(room_id, user_id):
return True, None, None
if utils.is_super_user(user_id):
return True, None, None
if utils.is_global_moderator(user_id) and utils.is_room_ephemeral(room_id):
return True, None, None
if utils.is_moderator(room_id, user_id) and utils.is_room_ephemeral(room_id):
return True, None, None
channel_id = utils.get_channel_for_room(room_id)
if utils.is_admin(channel_id, user_id):
return True, None, None
if utils.is_owner_channel(channel_id, user_id):
return True, None, None
return False, ECodes.NOT_ALLOWED, 'user {} is not allowed to {} the room'.format(str(user_id), action)
def on_remove_room(self, activity: Activity) -> (bool, int, str):
return self._remove_or_rename_room(activity, 'remove')
def on_rename_room(self, activity: Activity) -> (bool, int, str):
return self._remove_or_rename_room(activity, 'rename')
def on_disconnect(self, activity: Activity) -> (bool, int, str):
user_id = environ.env.session.get(SessionKeys.user_id.value)
user_name = environ.env.session.get(SessionKeys.user_name.value)
if user_id is None or not isinstance(user_id, str) or user_name is None:
return False, ECodes.NO_USER_IN_SESSION, 'no user in session, not connected'
return True, None, None
def on_report(self, activity: Activity) -> (bool, int, str):
if not hasattr(activity, 'object') or not hasattr(activity.object, 'content'):
return False, ECodes.MISSING_OBJECT_CONTENT, 'need object.content (reported message)'
if not hasattr(activity.object, 'id'):
return False, ECodes.MISSING_OBJECT_ID, 'need object.id (id of reported message)'
if not hasattr(activity, 'target') or not hasattr(activity.target, 'id'):
return False, ECodes.MISSING_TARGET_ID, 'need target.id (id of user reported)'
try:
activity.target.display_name = utils.get_user_name_for(activity.target.id)
except Exception as e:
logger.warning('could not get username for id %s: %s' % (str(activity.target.id), str(e)))
logger.exception(traceback.format_exc(e))
return False, ECodes.NO_SUCH_USER, 'no such user %s' % activity.target.id
if not utils.is_base64(activity.object.content):
return False, ECodes.NOT_BASE64, 'object.content is not base64 encoded'
if hasattr(activity.object, 'summary') and len(activity.object.summary.trim()) > 0:
if not utils.is_base64(activity.object.summary):
return False, ECodes.NOT_BASE64, 'object.summary is not base64 encoded'
return True, None, None
def on_ban(self, activity: Activity) -> (bool, int, str):
room_id = activity.target.id
target_type = activity.target.object_type
user_id = activity.actor.id
kicked_id = activity.object.id
ban_duration = activity.object.summary
is_global_ban = target_type == 'global' or room_id is None or room_id == ''
channel_id = None
if not is_global_ban:
if hasattr(activity, 'object') and hasattr(activity.object, 'url'):
channel_id = activity.object.url
if channel_id is None or len(channel_id.strip()) == 0:
channel_id = utils.get_channel_for_room(room_id)
try:
DurationValidator(ban_duration)
except ValueError as e:
return False, ECodes.INVALID_BAN_DURATION, 'invalid ban duration: %s' % str(e)
if not is_global_ban and room_id is not None and len(room_id.strip()) > 0:
try:
utils.get_room_name(room_id)
except NoSuchRoomException as e:
return False, ECodes.NO_SUCH_ROOM, 'no room found for uuid: %s' % str(e)
if kicked_id is None or kicked_id.strip() == '':
return False, ECodes.MISSING_OBJECT_ID, 'got blank user id, can not ban'
if not is_global_ban and not utils.room_exists(channel_id, room_id):
return False, ECodes.NO_SUCH_ROOM, 'no room with id "%s" exists' % room_id
if utils.is_super_user(user_id) or utils.is_global_moderator(user_id):
return True, None, None
if utils.is_super_user(kicked_id) or utils.is_global_moderator(kicked_id):
return False, ECodes.NO_SUCH_ROOM, 'not allowed to kick super users or global mobs'
if not is_global_ban:
if not utils.is_owner(room_id, user_id):
return False, ECodes.NOT_ALLOWED, 'only owners can ban'
elif not utils.is_admin(channel_id, user_id):
return False, ECodes.NOT_ALLOWED, 'only admins, super users and global mods can do global bans'
return True, None, None
def on_request_admin(self, activity: Activity) -> (bool, int, str):
activity.actor = Actor({
'id': str(environ.env.session.get(SessionKeys.user_id.value)),
'displayName': environ.env.session.get(SessionKeys.user_name.value)
})
room_id = activity.target.id
channel_id = utils.get_channel_for_room(room_id)
admin_room_id = utils.get_admin_room()
if admin_room_id is None or len(admin_room_id.strip()) == 0:
logger.error('no admin room found for channel "%s"' % channel_id)
return False, ECodes.NO_ADMIN_ROOM_FOUND, 'no admin room for this channel'
return True, None, None
def on_set_acl(self, activity: Activity) -> (bool, int, str):
def _can_edit_acl(_target_id: str, _user_id: str) -> bool:
object_type = activity.target.object_type
is_for_channel = object_type == 'channel'
if is_for_channel:
if utils.is_owner_channel(_target_id, _user_id):
return True
if utils.is_admin(_target_id, _user_id):
return True
else:
if utils.is_owner(_target_id, _user_id):
return True
channel_id = None
if hasattr(activity, 'object') and hasattr(activity.object, 'url'):
channel_id = activity.object.url
if channel_id is None or len(channel_id.strip()) == 0:
channel_id = utils.get_channel_for_room(_target_id)
if channel_id is not None and utils.is_owner_channel(channel_id, _user_id):
return True
if utils.is_super_user(_user_id) or utils.is_global_moderator(_user_id):
return True
return False
user_id = activity.actor.id
target_id = activity.target.id
object_type = activity.target.object_type
if object_type is None or len(object_type.strip()) == 0:
return False, ECodes.INVALID_TARGET_TYPE, 'empty object_type, must be one of [channel, room]'
if object_type not in ['channel', 'room']:
return False, ECodes.INVALID_TARGET_TYPE, \
'invalid object_type "%s", must be one of [channel, room]' % object_type
if not _can_edit_acl(target_id, user_id):
return False, ECodes.NOT_ALLOWED, 'user is not allowed to change acls on the target'
if utils.is_super_user(user_id) or utils.is_global_moderator(user_id):
return True, None, None
# validate all acls before actually changing anything
acls = activity.object.attachments
all_available_acls_types = environ.env.config.get(ConfigKeys.ACL)['available']['acls']
for acl in acls:
if acl.object_type not in all_available_acls_types:
return False, ECodes.INVALID_ACL_TYPE, 'invalid acl type "%s"' % acl.object_type
if acl.summary is None or acl.summary not in ApiActions.all_api_actions:
return False, ECodes.INVALID_ACL_ACTION, 'invalid api action "%s"' % acl.summary
is_valid, error_msg = validation.acl.is_acl_valid(acl.object_type, acl.content)
if not is_valid:
return False, ECodes.INVALID_ACL_VALUE, 'invalid acl value "%s" for type "%s": %s' % \
(acl.content, acl.object_type, error_msg)
return True, None, None
def on_join(self, activity: Activity) -> (bool, int, str):
room_id = activity.target.id
room_name = activity.target.display_name
user_id = environ.env.session.get(SessionKeys.user_id.value, None)
if user_id is None or len(user_id.strip()) == 0:
user_id = activity.actor.id
if room_id is not None and len(room_id.strip()) > 0:
try:
room_name = utils.get_room_name(room_id)
except NoSuchRoomException:
return False, ECodes.NO_SUCH_ROOM, 'room with id "{}" does not exist'.format(room_id)
else:
if room_name is None or len(room_name.strip()) == 0:
return False, ECodes.MISSING_TARGET_DISPLAY_NAME, 'neither room id nor name supplied'
try:
room_id = utils.get_room_id(room_name)
except NoSuchRoomException:
return False, ECodes.NO_SUCH_ROOM, 'room does not exists with given name "{}"'.format(room_name)
except MultipleRoomsFoundForNameException:
return False, ECodes.MULTIPLE_ROOMS_WITH_NAME, 'found multiple rooms with name "%s"' % room_name
if not hasattr(activity, 'object'):
activity.object = DefObject(dict())
if not utils.user_is_online(user_id):
user_name = '<unknown>'
try:
user_name = utils.get_user_name_for(user_id)
except NoSuchUserException:
logger.error('could not get username for user id %s' % user_id)
logger.warning(
'user "%s" (%s) is not online, not joining room "%s" (%s)!' %
(user_name, user_id, room_name, room_id))
return False, ECodes.NOT_ONLINE, 'user is not online'
if utils.is_super_user(user_id) or utils.is_global_moderator(user_id):
return True, None, None
if utils.is_owner(room_id, user_id):
return True, None, None
channel_id = utils.get_channel_for_room(room_id)
if utils.is_owner_channel(channel_id, user_id):
return True, None, None
# try real-time when joining
excluded_users = utils.get_excluded_users(user_id, skip_cache=True)
def should_exclude_room(owner):
# owner id 0 is the default "admin" owner of static rooms, no need to check them
if owner is None or not len(owner.strip()) or owner == "0":
return False
return owner in excluded_users
room_owners = utils.get_owners_for_room(room_id)
for owner_id in room_owners:
if should_exclude_room(owner_id):
return False, ECodes.NOT_ALLOWED, "matches ignore list"
activity.object.url = channel_id
activity.object.display_name = utils.get_channel_name(channel_id)
activity.target.object_type = 'room'
try:
acls = utils.get_acls_in_room_for_action(room_id, ApiActions.JOIN)
except NoSuchRoomException:
return False, ECodes.NO_SUCH_ROOM, 'no such room'
is_valid, error_msg = validation.acl.validate_acl_for_action(activity, ApiTargets.ROOM, ApiActions.JOIN, acls)
if not is_valid:
return False, ECodes.NOT_ALLOWED, error_msg
is_banned, info_dict = utils.is_banned(user_id, room_id)
if is_banned:
scope = info_dict['scope']
seconds_left = info_dict['seconds']
target_id = info_dict['id']
target_name = ''
if scope == 'room':
target_name = utils.get_room_name(target_id)
elif scope == 'channel':
target_name = utils.get_channel_name(target_id)
reason = utils.reason_for_ban(user_id, scope, target_id)
json_act = utils.activity_for_already_banned(seconds_left, reason, scope, target_id, target_name)
return False, ECodes.USER_IS_BANNED, json_act
return True, None, None
def on_leave(self, activity: Activity) -> (bool, int, str):
if not hasattr(activity, 'target') or not hasattr(activity.target, 'id'):
return False, ECodes.MISSING_TARGET_ID, 'room_id is None when trying to leave room'
return True, None, None
def on_list_channels(self, activity: Activity) -> (bool, int, str):
return True, None, None
def on_list_rooms(self, activity: Activity) -> (bool, int, str):
if not hasattr(activity, 'object') or not hasattr(activity.object, 'url'):
return False, ECodes.MISSING_OBJECT_URL, 'need channel ID to list rooms'
channel_id = activity.object.url
if channel_id is None or channel_id == '':
return False, ECodes.MISSING_OBJECT_URL, 'need channel ID to list rooms'
user_id = activity.actor.id
is_banned, duration = utils.is_banned_globally(user_id)
if is_banned:
environ.env.join_room(user_id)
reason = utils.reason_for_ban(user_id)
json_act = utils.activity_for_already_banned(duration, reason)
environ.env.emit(
'gn_banned', json_act, json=True, room=user_id, broadcast=False, include_self=True, namespace='/ws')
environ.env.disconnect()
logger.info('user %s is banned from chatting for: %ss' % (user_id, duration))
return False, ECodes.USER_IS_BANNED, json_act
activity.target = Target({'objectType': 'channel'})
acls = utils.get_acls_in_channel_for_action(channel_id, ApiActions.LIST)
is_valid, msg = validation.acl.validate_acl_for_action(activity, ApiTargets.CHANNEL, ApiActions.LIST, acls)
if not is_valid:
return False, ECodes.NOT_ALLOWED, msg
return True, None, None
def on_update_user_info(self, activity: Activity) -> (bool, int, str):
if not hasattr(activity, 'object'):
return False, ECodes.MISSING_OBJECT, 'no object on activity'
if not hasattr(activity.object, 'attachments'):
return False, ECodes.MISSING_OBJECT_ATTACHMENTS, 'no attachments on object'
if len(activity.object.attachments) == 0:
return False, ECodes.MISSING_OBJECT_ATTACHMENTS, 'no attachments on object'
attachments = activity.object.attachments
for attachment in attachments:
if not hasattr(attachment, 'object_type') or len(attachment.object_type.strip()) == 0:
logger.warn('no object.attachments.objectType specified')
return False, ECodes.MISSING_ATTACHMENT_TYPE, 'no objectType on attachment for object'
if not hasattr(attachment, 'content') or len(attachment.content.strip()) == 0:
logger.warn('no object.attachments.content specified')
return False, ECodes.MISSING_ATTACHMENT_CONTENT, 'no content on attachment for object'
if not utils.is_base64(attachment.content):
return False, ECodes.NOT_BASE64, 'content on attachment for object is not base64 encoded'
return True, None, None
def on_users_in_room(self, activity: Activity) -> (bool, int, str):
room_id = activity.target.id
if room_id is None or len(room_id.strip()) == 0:
return False, ECodes.MISSING_TARGET_ID, 'no room id specified'
return True, None, None
def on_history(self, activity: Activity) -> (bool, int, str):
room_id = activity.target.id
if room_id is None or room_id.strip() == '':
return False, ECodes.MISSING_TARGET_ID, 'invalid target id'
try:
acls = utils.get_acls_in_room_for_action(room_id, ApiActions.HISTORY)
except NoSuchRoomException:
return False, ECodes.NO_SUCH_ROOM, 'no such room'
is_valid, error_msg = validation.acl.validate_acl_for_action(
activity, ApiTargets.ROOM, ApiActions.HISTORY, acls)
if not is_valid:
return False, ECodes.NOT_ALLOWED, error_msg
return True, None, None
def _can_be_invisible(self, user_id: str):
if environ.env.config.get(ConfigKeys.INVISIBLE_UNRESTRICTED, default=False):
return True
if utils.is_super_user(user_id) or utils.is_global_moderator(user_id):
return True
return False
def _check_status(self, user_id, status: str) -> (bool, int, str):
if status not in ['online', 'offline', 'invisible']:
return False, ECodes.INVALID_STATUS, 'invalid status {}'.format(str(status))
if status == 'invisible' and not self._can_be_invisible(user_id):
return False, ECodes.NOT_ALLOWED, 'only ops can be invisible'
return True, None, None
def on_status(self, activity: Activity) -> (bool, int, str):
status = activity.verb
user_name = environ.env.session.get(SessionKeys.user_name.value, None)
user_id = environ.env.session.get(SessionKeys.user_id.value, None)
if user_name is None:
return False, ECodes.NO_USER_IN_SESSION, 'no user name in session'
is_valid, error_msg = validation.request.validate_request(activity)
if not is_valid:
return False, ECodes.NOT_ALLOWED, error_msg
if status not in ['online', 'offline', 'invisible']:
return False, ECodes.INVALID_STATUS, 'invalid status %s' % str(status)
if status == 'invisible' and not self._can_be_invisible(user_id):
return False, ECodes.NOT_ALLOWED, 'only ops can be invisible'
return True, None, None
def on_get_acl(self, activity: Activity) -> (bool, int, str):
if activity.target is None or not hasattr(activity.target, 'id'):
return False, ECodes.MISSING_TARGET_ID, 'no target on activity'
if activity.target.id is None or len(activity.target.id.strip()) == 0:
return False, ECodes.MISSING_TARGET_ID, 'blank target id on activity'
object_type = activity.target.object_type
if object_type is None or len(object_type.strip()) == 0:
return False, ECodes.INVALID_OBJECT_TYPE, 'blank object type on activity'
if object_type not in ['room', 'channel']:
return False, ECodes.INVALID_OBJECT_TYPE, 'unknown object type "%s", must be one of [channel, room]' % object_type
return True, None, None
def on_kick(self, activity: Activity) -> (bool, int, str):
kicker_id = activity.actor.id
room_id = activity.target.id
user_id_to_kick = activity.object.id
if room_id is None or room_id.strip() == '':
return False, ECodes.MISSING_TARGET_ID, 'got blank room id, can not kick'
try:
utils.get_room_name(room_id)
except NoSuchRoomException:
return False, ECodes.NO_SUCH_ROOM, 'no room with id "%s" exists' % room_id
if user_id_to_kick is None or user_id_to_kick.strip() == '':
return False, ECodes.MISSING_TARGET_DISPLAY_NAME, 'got blank user id, can not kick'
if utils.is_super_user(user_id_to_kick) or utils.is_global_moderator(user_id_to_kick):
return False, ECodes.NOT_ALLOWED, "not allowed to kick operators"
channel_id = utils.get_channel_for_room(room_id)
# so common, so skip checking ACLs table for this; owners/admins can kick, others not
if (
utils.is_owner(room_id, kicker_id) or
utils.is_super_user(kicker_id) or
utils.is_global_moderator(kicker_id) or
utils.is_admin(channel_id, kicker_id)
):
return True, None, None
return False, ECodes.NOT_ALLOWED, 'not allowed to kick'
def on_invite(self, activity: Activity) -> (bool, int, str):
if not hasattr(activity.actor, 'url'):
return False, ECodes.MISSING_ACTOR_URL, 'need invite room uuid in actor.url'
invite_room = activity.actor.url
if not hasattr(activity, 'target') or not hasattr(activity.target, 'id'):
return False, ECodes.MISSING_TARGET_ID, 'no target.id (uuid of user to invite)'
try:
activity.target.display_name = utils.get_user_name_for(activity.target.id)
except NoSuchUserException:
return False, ECodes.NO_SUCH_USER, 'no such user for target.id (uuid of user to invite)'
try:
channel_id = utils.get_channel_for_room(invite_room)
except (NoSuchRoomException, NoChannelFoundException):
return False, ECodes.NO_SUCH_ROOM, 'no room/channel found for actor.url room uuid'
if not utils.room_exists(channel_id, invite_room):
return False, ECodes.NO_SUCH_ROOM, 'room actor.url does not exist'
if not hasattr(activity, 'object'):
activity.object = DefObject(dict())
activity.object.url = channel_id
activity.object.display_name = utils.get_channel_name(channel_id)
return True, None, None
def on_whisper(self, activity: Activity) -> (bool, int, str):
if not hasattr(activity, 'target') or not hasattr(activity.target, 'id'):
return False, ECodes.MISSING_TARGET_ID, 'no target.id (user uuid to whisper to)'
if not hasattr(activity, 'actor') or not hasattr(activity.actor, 'id'):
return False, ECodes.MISSING_ACTOR_ID, 'no actor.id (id of user who is whispering)'
if not hasattr(activity, 'actor') or not hasattr(activity.actor, 'url'):
return False, ECodes.MISSING_ACTOR_URL, 'no actor.url (room uuid to whisper in)'
if not hasattr(activity, 'object') or not hasattr(activity.object, 'content'):
return False, ECodes.MISSING_OBJECT_CONTENT, 'no object.content (message to whisper)'
if not utils.is_base64(activity.object.content):
return False, ECodes.NOT_BASE64, 'object.content needs to be base64 encoded'
try:
activity.object.url = utils.get_channel_for_room(activity.actor.url)
except (NoSuchChannelException, NoChannelFoundException):
return False, ECodes.NO_SUCH_ROOM, 'no room found for actor.url (room uuid to whisper in)'
try:
activity.object.display_name = utils.get_channel_name(activity.object.url)
except (NoSuchChannelException, NoChannelFoundException):
return False, ECodes.NO_SUCH_CHANNEL, 'no channel found for actor.url (room uuid to whisper in)'
channel_acls = utils.get_acls_in_channel_for_action(activity.object.url, ApiActions.WHISPER)
is_valid, msg = validation.acl.validate_acl_for_action(
activity, ApiTargets.CHANNEL, ApiActions.WHISPER, channel_acls)
if not is_valid:
return False, ECodes.NOT_ALLOWED, msg
return True, None, None
def on_create(self, activity: Activity) -> (bool, int, str):
if not hasattr(activity, 'object') or not hasattr(activity.object, 'url'):
return False, ECodes.MISSING_OBJECT_URL, 'no channel id set'
if not hasattr(activity.target, 'display_name'):
return False, ECodes.MISSING_TARGET_DISPLAY_NAME, 'no room name set'
room_name = activity.target.display_name
channel_id = activity.object.url
if not hasattr(activity, 'actor') or not hasattr(activity.actor, 'id'):
return False, ECodes.MISSING_ACTOR_ID, 'need actor.id (user uuid)'
try:
activity.object.display_name = utils.get_channel_name(channel_id)
except NoSuchChannelException:
return False, ECodes.NO_SUCH_CHANNEL, 'channel does not exist'
if room_name is None or room_name.strip() == '':
return False, ECodes.MISSING_TARGET_DISPLAY_NAME, 'got blank room name, can not create'
if not utils.is_base64(room_name):
return False, ECodes.NOT_BASE64, 'invalid room name, not base64 encoded'
room_name = utils.b64d(room_name)
if not environ.env.db.channel_exists(channel_id):
return False, ECodes.NO_SUCH_CHANNEL, 'channel does not exist'
if utils.room_name_restricted(room_name):
return False, ECodes.ROOM_NAME_RESTRICTED, 'restricted room name'
if environ.env.db.room_name_exists(channel_id, room_name):
return False, ECodes.ROOM_ALREADY_EXISTS, 'a room with that name already exists'
if not hasattr(activity.target, 'object_type') or \
activity.target.object_type is None or \
len(str(activity.target.object_type).strip()) == 0:
# for acl validation to know we're trying to create a room
activity.target.object_type = 'room'
channel_acls = utils.get_acls_in_channel_for_action(channel_id, ApiActions.CREATE)
is_valid, msg = validation.acl.validate_acl_for_action(
activity, ApiTargets.CHANNEL, ApiActions.CREATE, channel_acls)
if not is_valid:
return False, ECodes.NOT_ALLOWED, msg
return True, None, None
def on_test(self, activity: Activity):
""" only used for testing decorators """
return True, None, None