Ejemplo n.º 1
0
    def verify_user_status(cls, user: User) -> None:

        if not user.is_active:

            cls.log_event(
                Events.refused_login,
                payload={
                    "username": user.email,
                    "motivation": "account not active"
                },
            )

            # Beware, frontend leverages on this exact message,
            # do not modified it without fix also on frontend side
            raise Forbidden("Sorry, this account is not active")

        now: Optional[datetime] = None

        if cls.DISABLE_UNUSED_CREDENTIALS_AFTER and user.last_login:

            if TESTING and user.email == cls.default_user:
                log.info(
                    "Default user can't be blocked for inactivity during tests"
                )
            else:
                now = get_now(user.last_login.tzinfo)
                if user.last_login + cls.DISABLE_UNUSED_CREDENTIALS_AFTER < now:
                    cls.log_event(
                        Events.refused_login,
                        payload={
                            "username": user.email,
                            "motivation": "account blocked due to inactivity",
                        },
                    )
                    raise Forbidden(
                        "Sorry, this account is blocked for inactivity")

        if user.expiration:
            # Reuse the now instance, if previously inizialized
            # tzinfo should be the same for both last_login and expiration fields
            if not now:
                now = get_now(user.expiration.tzinfo)

            if user.expiration < now:
                cls.log_event(
                    Events.refused_login,
                    payload={
                        "username": user.email,
                        "motivation": "account expired"
                    },
                )
                raise Forbidden("Sorry, this account is expired")
Ejemplo n.º 2
0
    def count_failed_login(self, username: str) -> int:

        failed_logins = self.get_logins(username, only_unflushed=True)
        if not failed_logins:
            return 0

        last_failed = failed_logins[-1]
        exp = last_failed.date + self.FAILED_LOGINS_EXPIRATION

        if get_now(exp.tzinfo) > exp:
            self.flush_failed_logins(username)
            return 0

        return len(failed_logins)
Ejemplo n.º 3
0
    def check_password_validity(
            self, user: User,
            totp_authentication: bool) -> Dict[str, List[str]]:

        # ##################################################
        # Check if something is missing in the authentication and ask additional actions
        # raises exceptions in case of errors

        message: Dict[str, List[str]] = {"actions": [], "errors": []}
        last_pwd_change = user.last_password_change
        if last_pwd_change is None or last_pwd_change == 0:
            last_pwd_change = EPOCH

        if self.FORCE_FIRST_PASSWORD_CHANGE and last_pwd_change == EPOCH:

            message["actions"].append("FIRST LOGIN")
            message["errors"].append("Please change your temporary password")

            self.log_event(Events.password_expired, user=user)

            if totp_authentication:

                message["qr_code"] = [self.get_qrcode(user)]

        elif self.MAX_PASSWORD_VALIDITY:

            valid_until = last_pwd_change + self.MAX_PASSWORD_VALIDITY

            # offset-naive datetime to compare with MySQL
            now = get_now(last_pwd_change.tzinfo)

            expired = last_pwd_change == EPOCH or valid_until < now

            if expired:

                message["actions"].append("PASSWORD EXPIRED")
                message["errors"].append(
                    "Your password is expired, please change it")

                self.log_event(Events.password_expired, user=user)

        return message
Ejemplo n.º 4
0
    def verify_token_validity(self, jti: str, user: User) -> bool:

        token_entry = self.db.Token.query.filter_by(jti=jti).first()

        if token_entry is None:
            return False
        if token_entry.user_id is None or token_entry.user_id != user.id:
            return False

        # offset-naive datetime to compare with MySQL
        now = get_now(token_entry.expiration.tzinfo)

        if now > token_entry.expiration:
            self.invalidate_token(token=token_entry.token)
            log.info(
                "This token is no longer valid: expired since {}",
                token_entry.expiration.strftime("%d/%m/%Y"),
            )
            return False

        # Verify IP validity only after grace period is expired
        if token_entry.creation + self.GRACE_PERIOD < now:
            ip = self.get_remote_ip()
            if token_entry.IP != ip:
                log.warning(
                    "This token is emitted for IP {}, invalid use from {}",
                    token_entry.IP,
                    ip,
                )
                return False

        if token_entry.last_access + self.SAVE_LAST_ACCESS_EVERY < now:
            token_entry.last_access = now

            try:
                self.db.session.add(token_entry)
                self.db.session.commit()
            except Exception as e:  # pragma: no cover
                log.error("DB error ({}), rolling back", e)
                self.db.session.rollback()

        return True