pip_facades_sample_python/operations/version1/SessionOperationsV1.py

# -*- coding: utf-8 -*-
from typing import List

import bottle
from pip_services4_components.config import ConfigParams
from pip_services4_commons.convert import JsonConverter, TypeCode
from pip_services4_data.query import FilterParams
from pip_services4_commons.errors import UnauthorizedException, BadRequestException
from pip_services4_components.refer import IReferences, Descriptor
from pip_services4_http.controller import RestOperations
from pip_services4_http.controller.HttpRequestDetector import HttpRequestDetector

from pip_facades_sample_python.clients.version1.AccountV1 import AccountV1
from pip_facades_sample_python.clients.version1.EmailSettingsV1 import EmailSettingsV1
from pip_facades_sample_python.clients.version1.IAccountsClientV1 import IAccountsClientV1
from pip_facades_sample_python.clients.version1.IEmailSettingsClientV1 import IEmailSettingsClientV1
from pip_facades_sample_python.clients.version1.IInvitationsClientV1 import IInvitationsClientV1
from pip_facades_sample_python.clients.version1.IPasswordsClientV1 import IPasswordsClientV1
from pip_facades_sample_python.clients.version1.IRolesClientV1 import IRolesClientV1
from pip_facades_sample_python.clients.version1.ISessionsClientV1 import ISessionsClientV1
from pip_facades_sample_python.clients.version1.ISettingsClientV1 import ISettingsClientV1
from pip_facades_sample_python.clients.version1.ISitesClientV1 import ISitesClientV1
from pip_facades_sample_python.clients.version1.SessionV1 import SessionV1
from pip_facades_sample_python.clients.version1.SiteV1 import SiteV1
from pip_facades_sample_python.clients.version1.UserPasswordInfoV1 import UserPasswordInfoV1
from pip_facades_sample_python.operations.version1.SessionUserV1 import SessionUserV1


class SessionsOperationsV1(RestOperations):
    __default_config = ConfigParams.from_tuples(
        'options.cookie_enabled', True,
        'options.cookie', 'x-session-id',
        'options.max_cookie_age', 365 * 24 * 60 * 60 * 1000
    )

    def __init__(self):
        super().__init__()

        self.__cookie: str = 'x-session-id'
        self.__cookie_enabled: bool = True
        self.__max_cookie_age: float = 365 * 24 * 60 * 60 * 1000
        self.__settings_client: ISettingsClientV1 = None
        self.__accounts_client: IAccountsClientV1 = None
        self.__sessions_client: ISessionsClientV1 = None
        self.__passwords_client: IPasswordsClientV1 = None
        self.__roles_client: IRolesClientV1 = None
        self.__email_settings_client: IEmailSettingsClientV1 = None
        self.__sites_client: ISitesClientV1 = None
        self.__invitations_client: IInvitationsClientV1 = None

        self._dependency_resolver.put('settings', Descriptor('pip-services-settings', 'client', '*', '*', '1.0'))
        self._dependency_resolver.put('accounts', Descriptor('pip-services-accounts', 'client', '*', '*', '1.0'))
        self._dependency_resolver.put('passwords', Descriptor('pip-services-passwords', 'client', '*', '*', '1.0'))
        self._dependency_resolver.put('roles', Descriptor('pip-services-roles', 'client', '*', '*', '1.0'))
        self._dependency_resolver.put('emailsettings',
                                      Descriptor('pip-services-emailsettings', 'client', '*', '*', '1.0'))
        self._dependency_resolver.put('sessions', Descriptor('pip-services-sessions', 'client', '*', '*', '1.0'))
        self._dependency_resolver.put('sites', Descriptor('pip-services-sites', 'client', '*', '*', '1.0'))
        self._dependency_resolver.put('invitations', Descriptor('pip-services-invitations', 'client', '*', '*', '1.0'))

    def configure(self, config: ConfigParams):
        config = config.set_defaults(SessionsOperationsV1.__default_config)
        self._dependency_resolver.configure(config)

        self.__cookie_enabled = config.get_as_boolean_with_default('options.cookie_enabled', self.__cookie_enabled)
        self.__cookie = config.get_as_string_with_default('options.cookie', self.__cookie)
        self.__max_cookie_age = config.get_as_long_with_default('options.max_cookie_age', self.__max_cookie_age)

    def set_references(self, references: IReferences):
        super().set_references(references)

        self.__settings_client = self._dependency_resolver.get_one_required('settings')
        self.__sessions_client = self._dependency_resolver.get_one_required('sessions')
        self.__accounts_client = self._dependency_resolver.get_one_required('accounts')
        self.__passwords_client = self._dependency_resolver.get_one_required('passwords')
        self.__roles_client = self._dependency_resolver.get_one_required('roles')
        self.__email_settings_client = self._dependency_resolver.get_one_required('emailsettings')
        self.__sites_client = self._dependency_resolver.get_one_required('sites')
        self.__invitations_client = self._dependency_resolver.get_one_required('invitations')

    def load_session(self):
        # parse headers first, and if nothing in headers get cookie
        session_id = bottle.request.headers.get('x-session-id')

        session, err = None, None

        if session_id:
            try:
                session = self.__sessions_client.get_session_by_id('facade', session_id)
                if session is None:
                    raise UnauthorizedException(
                        'facade',
                        'SESSION_NOT_FOUND',
                        'Session invalid or already expired.'
                    ).with_details('session_id', session_id).with_status(440)
            except Exception as err:
                return self._send_error(err)

            if err is None and session:
                # Associate session user with the request
                bottle.request.user_id = session.user_id
                bottle.request.user_name = session.user_name
                bottle.request.user = session.user
                bottle.request.session_id = session.id
            else:
                return self._send_error(err)

    def open_session(self, account: AccountV1, roles: List[str], ):
        try:
            session: SessionV1 = None
            sites: List[SiteV1] = None
            passwordInfo: UserPasswordInfoV1 = None
            settings: ConfigParams = None

            # Retrieve sites for user

            site_roles = [] if not roles else list(filter(lambda x: x.find(':') > 0, roles))
            site_ids = [] if not site_roles else list(map(lambda x: x[0:x.find(':')] if x.find(':') >= 0 else x, site_roles))

            if len(site_ids) > 0:
                filter_params = FilterParams.from_tuples('ids', site_ids)
                page = self.__sites_client.get_sites(None, filter_params, None)
                sites = [] if page is None else page.data
            else:
                sites = []

            password_info = self.__passwords_client.get_password_info(None, account.id)

            settings = self.__settings_client.get_section_by_id(None, account.id)

            # Open a new user session
            user = SessionUserV1(
                id=account.id,
                name=account.name,
                login=account.login,
                create_time=account.create_time,
                time_zone=account.time_zone,
                language=account.language,
                theme=account.theme,
                roles=roles,
                sites=list(map(lambda x: {'id': x.id, 'name': x.name}, sites)),
                settings=settings,
                change_pwd_time=None if password_info is None else password_info.change_time,
                custom_hdr=account.custom_hdr,
                custom_dat=account.custom_dat
            )

            address = HttpRequestDetector.detect_address(bottle.request)
            client = HttpRequestDetector.detect_browser(bottle.request)
            platform = HttpRequestDetector.detect_platform(bottle.request)

            session = self.__sessions_client.open_session(None, account.id, account.name, address, client, user, None)
            return JsonConverter.to_json(session)
        except Exception as err:
            return self._send_error(err)

    def signup(self):
        try:
            signup_data = bottle.request.json if isinstance(bottle.request.json, dict) else JsonConverter.from_json(
                TypeCode.Map, bottle.request.json)
            account: AccountV1 = None
            invited: bool = False
            roles: List[str] = []

            # Validate password first
            # Todo: complete implementation after validate password is added

            # Create account
            new_account = AccountV1(
                name=signup_data.get('name'),
                login=signup_data.get('login') or signup_data.get('email'),
                language=signup_data.get('language'),
                theme=signup_data.get('theme'),
                time_zone=signup_data.get('time_zone')
            )

            account = self.__accounts_client.create_account(None, new_account)

            # Create password for the account
            password = signup_data.get('password')

            self.__passwords_client.set_password(None, account.id, password)

            # Activate all pending invitations
            email = signup_data.get('email')

            invitations = self.__invitations_client.activate_invitations(None, email, account.id)
            if invitations:
                # Calculate user roles from activated invitations
                for invitation in invitations:
                    # Was user invited with the same email?
                    invited = invited or email == invitation.invitee_email

                    if invitation.site_id:
                        invitation.role = invitation.role or 'user'
                        role = invitation.site_id + ':' + invitation.role
                        roles.append(role)

            # Create email settings for the account
            new_email_settings = EmailSettingsV1(
                id=account.id,
                name=account.name,
                email=email,
                language=account.language
            )

            if self.__email_settings_client is not None:
                if invited:
                    self.__email_settings_client.set_verified_settings(None, new_email_settings)
                else:
                    self.__email_settings_client.set_settings(None, new_email_settings)

            return self.open_session(account, roles)
        except Exception as err:
            return self._send_error(err)

    def signup_validate(self):
        login = dict(bottle.request.query.decode()).get('login')

        if login:
            try:
                account = self.__accounts_client.get_account_by_id_or_login(None, login)
                if account:
                    raise BadRequestException(
                        None, 'LOGIN_ALREADY_USED',
                        'Login ' + login + ' already being used'
                    ).with_details('login', login)

                return bottle.HTTPResponse(status=204)

            except Exception as err:
                return self._send_error(err)
        else:
            return bottle.HTTPResponse(status=204)

    def signin(self):
        json_data = bottle.request.json if isinstance(bottle.request.json, dict) else JsonConverter.from_json(
            TypeCode.Map, bottle.request.json)

        login = json_data.get('login')
        password = json_data.get('password')

        account: AccountV1 = None
        roles: List[str] = []

        # Find user account
        try:
            account = self.__accounts_client.get_account_by_id_or_login(None, login)

            if account is None:
                raise BadRequestException(
                    None,
                    'WRONG_LOGIN',
                    'Account ' + login + ' was not found'
                ).with_details('login', login)

            # Authenticate user
            result = self.__passwords_client.authenticate(None, account.id, password)
            # wrong password error is UNKNOWN when use http client
            if result is False:
                raise BadRequestException(
                    None,
                    'WRONG_PASSWORD',
                    'Wrong password for account ' + login
                ).with_details('login', login)

            # Retrieve user roles
            if self.__roles_client:
                roles = self.__roles_client.get_roles_by_id(None, account.id, )
            else:
                roles = []

            return self.open_session(account, roles)
        except Exception as err:
            return self._send_error(err)

    def signout(self):
        if hasattr(bottle.request, 'session_id'):
            try:
                self.__sessions_client.close_session(None, bottle.request.session_id)
                return bottle.HTTPResponse(status=204)
            except Exception as err:
                self._send_error(err)

        return bottle.HTTPResponse(status=204)

    def get_sessions(self):
        filter_params = self._get_filter_params()
        paging = self._get_paging_params()

        result = self.__sessions_client.get_sessions(None, filter_params, paging)
        return self._send_result(result)

    def restore_session(self):
        try:
            session_id = dict(bottle.request.query.decode()).get('session_id')

            session = self.__sessions_client.get_session_by_id(None, session_id)

            # If session closed then return null
            if session and not session.active:
                session = None

            if session:
                return JsonConverter.to_json(session)
            else:
                return bottle.HTTPResponse(status=204)
        except Exception as err:
            return self._send_error(err)

    def get_user_sessions(self, user_id):
        filter_params = self._get_filter_params()
        paging = self._get_paging_params()

        filter_params.set_as_object('user_id', user_id)

        sessions = self.__sessions_client.get_sessions(None, filter_params, paging)
        return self._send_result(sessions.to_json())

    def get_current_session(self):
        # parse headers first, and if nothing in headers get cookie
        session_id = bottle.request.headers.get('x-session-id')

        session = self.__sessions_client.get_session_by_id(None, session_id)
        return self._send_result(session)

    def close_session(self, user_id, session_id):

        result = self.__sessions_client.close_session(None, session_id)
        return self._send_result(result)