dino/hooks/login.py
import logging
import sys
import traceback
from datetime import datetime as dt
import activitystreams
from dino.utils.activity_helper import ActivityBuilder
from dino import environ
from dino import utils
from dino import validation
from dino.config import SessionKeys, ConfigKeys, ApiActions, ApiTargets
from dino.config import UserKeys
from dino.exceptions import NoSuchUserException
logger = logging.getLogger(__name__)
class OnLoginHooks(object):
@staticmethod
def update_session_and_join_private_room(arg: tuple) -> None:
data, activity = arg
user_id = activity.actor.id
user_name = utils.b64d(activity.actor.display_name)
environ.env.session[SessionKeys.user_id.value] = user_id
environ.env.session[SessionKeys.user_name.value] = user_name
user_agent_string = ''
user_agent_platform = ''
user_agent_browser = ''
user_agent_version = ''
user_agent_language = ''
environ.env.session[SessionKeys.user_agent.value] = user_agent_string or ''
environ.env.session[SessionKeys.user_agent_browser.value] = user_agent_browser or ''
environ.env.session[SessionKeys.user_agent_version.value] = user_agent_version or ''
environ.env.session[SessionKeys.user_agent_platform.value] = user_agent_platform or ''
environ.env.session[SessionKeys.user_agent_language.value] = user_agent_language or ''
user_info = {
SessionKeys.avatar.value: environ.env.session.get(SessionKeys.avatar.value) or '',
SessionKeys.app_avatar.value: environ.env.session.get(SessionKeys.app_avatar.value) or '',
SessionKeys.app_avatar_safe.value: environ.env.session.get(SessionKeys.app_avatar_safe.value) or '',
SessionKeys.age.value: environ.env.session.get(SessionKeys.age.value) or '',
SessionKeys.gender.value: environ.env.session.get(SessionKeys.gender.value) or '',
SessionKeys.user_type.value: environ.env.session.get(SessionKeys.user_type.value) or '',
SessionKeys.membership.value: environ.env.session.get(SessionKeys.membership.value) or '',
SessionKeys.group.value: environ.env.session.get(SessionKeys.group.value) or '',
SessionKeys.country.value: environ.env.session.get(SessionKeys.country.value) or '',
SessionKeys.has_webcam.value: environ.env.session.get(SessionKeys.has_webcam.value) or '',
SessionKeys.fake_checked.value: environ.env.session.get(SessionKeys.fake_checked.value) or '',
SessionKeys.is_streaming.value: environ.env.session.get(SessionKeys.is_streaming.value) or '',
SessionKeys.enabled_safe.value: environ.env.session.get(SessionKeys.enabled_safe.value) or '',
'last_login': dt.utcnow()
}
environ.env.db.set_user_info(user_id, user_info)
if activity.actor.image is None:
environ.env.session['image_url'] = ''
environ.env.session[SessionKeys.image.value] = 'n'
else:
environ.env.session['image_url'] = activity.actor.image.url
environ.env.session[SessionKeys.image.value] = 'y'
sid = environ.env.request.sid
utils.create_or_update_user(user_id, user_name)
utils.add_sid_for_user_id(user_id, sid)
environ.env.join_room(user_id)
environ.env.join_room(environ.env.request.sid)
@staticmethod
def publish_activity(arg: tuple) -> None:
data, activity = arg
user_id = activity.actor.id
user_name = environ.env.session.get(SessionKeys.user_name.value)
user_status = utils.get_user_status(user_id)
activity_json = utils.activity_for_login(
user_id, user_name, encode_attachments=False, user_status=user_status)
# invisible shouldn't get their last online at updated, so use the previous known time
if user_status == UserKeys.STATUS_INVISIBLE:
utils.add_last_online_at_to_event(activity_json, use_now=False)
else:
utils.add_last_online_at_to_event(activity_json, use_now=True)
environ.env.publish(activity_json, external=True)
@staticmethod
def reset_temp_session_values(arg: tuple):
_, activity = arg
for key in SessionKeys.temporary_keys.value:
environ.env.auth.update_session_for_key(activity.actor.id, key, False)
@staticmethod
def set_user_online_if_not_previously_invisible(arg: tuple) -> None:
data, activity = arg
user_id = activity.actor.id
user_status = utils.get_user_status(user_id, skip_cache=True)
invisible_login = False
# if the rest api is not called before login to set the status to invisible, it can be
# specified on the login request, causing the 'last_online_at' to not be updated
if hasattr(activity.actor, "content"):
content = activity.actor.content
invisible_login = content == "invisible"
logger.info("invisible login for {}? {}".format(user_id, invisible_login))
if utils.is_super_user(user_id) or utils.is_global_moderator(user_id):
try:
info_message = \
'op {} ({}) signed in; ' \
'user status is currently set to {}; ' \
'if not "3" (invisible), I will now change it to "1" (online)'
info_message = info_message.format(
user_id, utils.get_user_name_for(user_id), user_status
)
logger.info(info_message)
except NoSuchUserException:
logger.error('no username found for op user {}'.format(user_id))
except Exception as e:
logger.error('exception while getting username for op {}: {}'.format(user_id, str(e)))
logger.exception(e)
environ.env.capture_exception(sys.exc_info())
if user_status == UserKeys.STATUS_INVISIBLE or invisible_login:
# if login after server restart the cache value user:status:<user id> is non-existent, re-set to invisible
environ.env.cache.set_user_invisible(user_id, update_last_online=False)
environ.env.db.set_user_status_invisible(user_id)
else:
logger.info('setting user {} to online'.format(user_id))
# update last online as well, just in case there's an issue and we can't do it during disconnect
environ.env.db.set_user_online(user_id, update_last_online=True)
@staticmethod
def autojoin_rooms(arg: tuple) -> None:
data, activity = arg
if not str(environ.env.config.get(ConfigKeys.AUTOJOIN_ENABLED, 'false')).lower() in {'true', 'yes', '1', 'y'}:
return
try:
room_acls = environ.env.db.get_room_acls_for_action(ApiActions.AUTOJOIN)
except Exception as e:
logger.error('could not get autojoin acls: {}'.format(str(e)))
return
for room_id, acls in room_acls.items():
# sometimes room_id is None, if no autojoin rooms exist
if room_id is None or len(room_id.strip()) == 0:
continue
# needed for validation
join_data = data.copy()
join_data['target'] = {
'id': room_id,
'objectType': 'room'
}
is_valid, error_msg = validation.acl.validate_acl_for_action(
activitystreams.parse(join_data),
ApiTargets.ROOM,
ApiActions.JOIN,
acls
)
if not is_valid:
continue
join_data = ActivityBuilder.enrich({
'verb': 'join',
'actor': {
'id': activity.actor.id
},
'target': {
'id': room_id,
'objectType': 'room',
'content': 'autojoin'
}
})
environ.env.observer.emit('on_join', (join_data, activitystreams.parse(join_data)))
@environ.env.observer.on('on_login')
def _on_login_set_user_online(arg: tuple) -> None:
OnLoginHooks.set_user_online_if_not_previously_invisible(arg)
OnLoginHooks.update_session_and_join_private_room(arg)
OnLoginHooks.reset_temp_session_values(arg)
OnLoginHooks.autojoin_rooms(arg)
OnLoginHooks.publish_activity(arg)