Exemplo n.º 1
0
    def login(self,
              ctx,
              _ok=status_code.ok,
              _now=datetime.utcnow,
              _timedelta=timedelta,
              _dummy_password=uuid4().hex,
              is_logged_in_ext=True):
        """ Logs a user in, returning session info on success or raising ValidationError on any error.
        """
        # type: (LoginCtx, unicode, datetime, timedelta, unicode, bool) -> SessionInfo

        # Look up user and raise exception if not found by username
        with closing(self.odb_session_func()) as session:

            if ctx.input.get('username'):
                user = get_user_by_username(
                    session, ctx.input['username'])  # type: SSOUser
            else:
                user = get_user_by_id(session,
                                      ctx.input['user_id'])  # type: SSOUser

            # If the user is already logged in externally, this flag will be True,
            # in which case we do not check the credentials - we already know they are valid
            # because they were checked externally and user_id is the SSO user linked to the
            # already validated external credentials.
            if not is_logged_in_ext:

                # Check credentials first to make sure that attackers do not learn about any sort
                # of metadata (e.g. is the account locked) if they do not know username and password.
                if not self._check_credentials(
                        ctx, user.password if user else _dummy_password):
                    raise ValidationError(status_code.auth.not_allowed, False)

                # Check input TOTP key if two-factor authentication is enabled
                if user.is_totp_enabled:
                    input_totp_code = ctx.input.get('totp_code')
                    if not input_totp_code:
                        logger.warn('Missing TOTP code; user `%s`',
                                    user.username)
                        raise ValidationError(status_code.auth.not_allowed,
                                              False)
                    else:
                        user_totp_key = self.decrypt_func(user.totp_key)
                        if not CryptoManager.verify_totp_code(
                                user_totp_key, input_totp_code):
                            logger.warn('Invalid TOTP code; user `%s`',
                                        user.username)
                            raise ValidationError(status_code.auth.not_allowed,
                                                  False)

            # It must be possible to log into the application requested (CRM above)
            self._check_login_to_app_allowed(ctx)

            # Common auth checks
            self._run_user_checks(ctx, user)

            # We assume that we will not have to warn about an approaching password expiry
            has_w_about_to_exp = False

            # If applicable, password may be about to expire (this must be after checking that it has not already).
            # Note that it may return a specific status to return (warning or error)
            _about_status = self._check_password_about_to_expire(user)
            if _about_status is not True:
                if _about_status == status_code.warning:
                    has_w_about_to_exp = True
                else:
                    raise ValidationError(status_code.password.e_about_to_exp,
                                          False, _about_status)

            # If password is marked as requiring a change upon next login but a new one was not sent, reject the request.
            self._check_must_send_new_password(ctx, user)

            # If new password is required, we need to validate and save it before session can be created.
            # Note that at this point we already know that the old password was correct so it is safe to set the new one
            # if it is confirmed to be valid. We also know that there is some new password on input because otherwise
            # the check above would have raised a ValidationError.
            if user.password_must_change:
                try:
                    validate_password(self.sso_conf,
                                      ctx.input.get('new_password'))
                except ValidationError as e:
                    if e.return_status:
                        raise ValidationError(e.sub_status, e.return_status,
                                              e.status)
                else:
                    set_password(self.odb_session_func, self.encrypt_func,
                                 self.hash_func, self.sso_conf, user.user_id,
                                 ctx.input['new_password'], False)

            # All validated, we can create a session object now
            creation_time = _now()
            expiration_time = creation_time + timedelta(
                minutes=self.sso_conf.session.expiry)
            ust = new_user_session_token()

            # Create current interaction details for this session
            session_state_change_list = []
            self.update_session_state_change_list(session_state_change_list,
                                                  ctx.remote_addr,
                                                  ctx.user_agent, 'login',
                                                  creation_time)
            opaque = {'session_state_change_list': session_state_change_list}

            session.execute(SessionModelInsert().values({
                'ust':
                ust,
                'creation_time':
                creation_time,
                'expiration_time':
                expiration_time,
                'user_id':
                user.id,
                'auth_type':
                const.auth_type.default,
                'auth_principal':
                user.username,
                'remote_addr':
                ', '.join(str(elem) for elem in ctx.remote_addr),
                'user_agent':
                ctx.user_agent,
                'ext_session_id':
                ctx.ext_session_id,
                GENERIC.ATTR_NAME:
                dumps(opaque)
            }))
            session.commit()

            info = SessionInfo()
            info.username = user.username
            info.user_id = user.user_id
            info.ust = self.encrypt_func(ust.encode('utf8'))
            info.creation_time = creation_time
            info.expiration_time = expiration_time
            info.has_w_about_to_exp = has_w_about_to_exp

            return info
Exemplo n.º 2
0
    def login(self,
              ctx,
              _ok=status_code.ok,
              _now=datetime.utcnow,
              _timedelta=timedelta):
        """ Logs a user in, returning session info on success or raising ValidationError on any error.
        """
        # Look up user and raise exception if not found by username
        with closing(self.odb_session_func()) as session:
            user = get_user_by_username(session, ctx.input['username'])
            if not user:
                raise ValidationError(status_code.auth.not_allowed, False)

            # Check credentials first to make sure that attackers do not learn about any sort
            # of metadata (e.g. is the account locked) if they do not know username and password.
            if not self._check_credentials(ctx, user):
                raise ValidationError(status_code.auth.not_allowed, False)

            # It must be possible to log into the application requested (CRM above)
            self._check_login_to_app_allowed(ctx)

            # Common auth checks
            self._run_user_checks(ctx, user)

            # If applicable, password may not be about to expire (this must be after checking that it has not already).
            # Note that it may return a specific status to return (warning or error)
            _about_status = self._check_password_about_to_expire(user)
            if _about_status is not True:
                if _about_status == status_code.warning:
                    _status_code = status_code.password.w_about_to_exp
                    inform = True
                else:
                    _status_code = status_code.password.e_about_to_exp
                    inform = False

                raise ValidationError(_status_code, inform, _about_status)

            # If password is marked as as requiring a change upon next login but a new one was not sent, reject the request.
            self._check_must_send_new_password(ctx, user)

            # If new password is required, we need to validate and save it before session can be created.
            # Note that at this point we already know that the old password was correct so it is safe to set the new one
            # if it is confirmed to be valid. We also know that there is some new password on input because otherwise
            # the check above would have raised a ValidationError.
            if user.password_must_change:
                try:
                    validate_password(self.sso_conf, ctx.input['new_password'])
                except ValidationError as e:
                    if e.return_status:
                        raise ValidationError(e.sub_status, e.return_status,
                                              e.status)
                else:
                    set_password(self.odb_session_func, self.encrypt_func,
                                 self.hash_func, self.sso_conf, user.user_id,
                                 ctx.input['new_password'], False)

            # All validated, we can create a session object now
            creation_time = _now()
            expiration_time = creation_time + timedelta(
                minutes=self.sso_conf.session.expiry)
            ust = new_user_session_token()

            session.execute(SessionModelInsert().values({
                'ust':
                ust,
                'creation_time':
                creation_time,
                'expiration_time':
                expiration_time,
                'user_id':
                user.id,
                'remote_addr':
                ', '.join(str(elem) for elem in ctx.remote_addr),
                'user_agent':
                ctx.user_agent,
            }))
            session.commit()

            info = SessionInfo()
            info.username = user.username
            info.user_id = user.user_id
            info.ust = self.encrypt_func(ust.encode('utf8'))
            info.creation_time = creation_time
            info.expiration_time = expiration_time

            return info