Esempio n. 1
0
    def verify_HOTP(self, token):
        """Verify the HOTP token.

        raises AuthenticationError if token is invalid or has expired (older than 1 hour).
        If the token is valid, the counter is incremented, to prohibit re-use.
        """
        hotp = twofactor_hotp.HOTP(self.hotp_secret, 8, hashes.SHA512())
        if self.hotp_issue_time is None:
            raise AuthenticationError(
                "No one-time authentication code currently issued.")
        timediff = dds_web.utils.current_time() - self.hotp_issue_time
        if timediff > datetime.timedelta(minutes=15):
            raise AuthenticationError(
                "One-time authentication code has expired.")

        try:
            hotp.verify(token, self.hotp_counter)
        except twofactor_InvalidToken as exc:
            raise AuthenticationError(
                "Invalid one-time authentication code.") from exc

        # Token verified, increment counter to prohibit re-use
        self.hotp_counter += 1
        # Reset the hotp_issue_time to allow a new code to be issued
        self.hotp_issue_time = None
        db.session.commit()
Esempio n. 2
0
def __verify_general_token(token):
    """Verifies the format, signature and expiration time of an encrypted and signed JWT token.

    Raises AuthenticationError if token is invalid or absent, could raise other exceptions from
    dependencies. On successful verification, it returns a dictionary of the claims in the token.
    """
    # Token required
    if not token:
        raise AuthenticationError(message="No token")

    # Verify token signature if signed or decrypt first if encrypted
    try:
        data = (verify_token_signature(token=token) if token.count(".") == 2
                else decrypt_and_verify_token_signature(token=token))
    except (ValueError, jwcrypto.common.JWException) as e:
        # ValueError is raised when the token doesn't look right (for example no periods)
        # jwcryopto.common.JWException is the base exception raised by jwcrypto,
        # and is raised when the token is malformed or invalid.
        flask.current_app.logger.exception(e)
        raise AuthenticationError(message="Invalid token") from e

    expiration_time = data.get("exp")
    # Use a hard check on top of the one from the dependency
    # exp shouldn't be before now no matter what
    if expiration_time and (dds_web.utils.current_time() <=
                            datetime.datetime.fromtimestamp(expiration_time)):
        return data

    raise AuthenticationError(message="Expired token")
Esempio n. 3
0
def __base_verify_token_for_invite(token):
    """Verify token and return claims."""
    claims = __verify_general_token(token=token)

    # Subject (user) not a valid entry
    if claims.get("sub"):
        raise AuthenticationError(message="Invalid token")

    return claims
Esempio n. 4
0
def verify_token_signature(token):
    """Verify the signature of the token.

    Return the claims such as subject/username on valid signature.
    """
    # Get key used for signing
    key = jwk.JWK.from_password(flask.current_app.config.get("SECRET_KEY"))

    # Verify token
    try:
        jwttoken = jwt.JWT(key=key, jwt=token, algs=["HS256"])
        return json.loads(jwttoken.claims)
    except jwt.JWTExpired as exc:
        # jwt dependency uses a 60 seconds leeway to check exp
        # it also prints out a stack trace for it, so we handle it here
        raise AuthenticationError(message="Expired token") from exc
    except ValueError as exc:
        # "Token format unrecognized"
        raise AuthenticationError(message="Invalid token") from exc
Esempio n. 5
0
def verify_password_reset_token(token):
    claims = __verify_general_token(token)
    user = __user_from_subject(claims.get("sub"))
    if user:
        rst = claims.get("rst")
        del claims
        gc.collect()
        if rst and rst == "pwd":
            return user
    raise AuthenticationError(message="Invalid token")
Esempio n. 6
0
def verify_token(token):
    """Verify token used in token authentication."""
    claims = __verify_general_token(token=token)

    if claims.get("rst"):
        raise AuthenticationError(message="Invalid token")

    user = __user_from_subject(subject=claims.get("sub"))

    if user.password_reset:
        token_expired = claims.get("exp")
        token_issued = datetime.datetime.fromtimestamp(
            token_expired) - MFA_EXPIRES_IN
        password_reset_row = user.password_reset[0]
        if not password_reset_row.valid and password_reset_row.changed > token_issued:
            raise AuthenticationError(
                message=("Password reset performed after last authentication. "
                         "Start a new authenticated session to proceed."))
    return __handle_multi_factor_authentication(
        user=user, mfa_auth_time_string=claims.get("mfa_auth_time"))
Esempio n. 7
0
def verify_invite_token(token):
    """Verify token sent in user invite."""
    claims = __base_verify_token_for_invite(token=token)

    # Email information required
    email = claims.get("inv")
    if not email:
        raise AuthenticationError(message="Invalid token")

    return email, models.Invite.query.filter(
        models.Invite.email == email).first()
Esempio n. 8
0
def decrypt_token(token):
    """Decrypt the encrypted token.

    Return the signed token embedded inside.
    """
    # Get key used for encryption
    key = jwk.JWK.from_password(flask.current_app.config.get("SECRET_KEY"))
    # Decrypt token
    try:
        decrypted_token = jwt.JWT(key=key, jwt=token)
    except ValueError as exc:
        # "Token format unrecognized"
        raise AuthenticationError(message="Invalid token") from exc

    return decrypted_token.claims
Esempio n. 9
0
def __handle_multi_factor_authentication(user, mfa_auth_time_string):
    """Verify multifactor authentication time frame."""
    if user:
        if mfa_auth_time_string:
            mfa_auth_time = datetime.datetime.fromtimestamp(
                mfa_auth_time_string)
            if mfa_auth_time >= dds_web.utils.current_time() - MFA_EXPIRES_IN:
                return user

        send_hotp_email(user)

        if flask.request.path.endswith("/user/second_factor"):
            return user

        raise AuthenticationError(
            message=
            "Two-factor authentication is required! Please check your primary e-mail!"
        )
Esempio n. 10
0
def extract_token_invite_key(token):
    """Verify token, email and invite.

    Return invite and temporary key.
    """
    claims = __base_verify_token_for_invite(token=token)

    # Verify email in token
    email = claims.get("inv")
    if not email:
        raise AuthenticationError(message="Invalid token")

    # Verify that there's an invite for the current email
    invite = models.Invite.query.filter(models.Invite.email == email).first()
    if not invite:
        raise InviteError(message="Invite could not be found!")

    try:
        return invite, bytes.fromhex(claims.get("sen_con"))
    except ValueError as exc:
        raise ValueError(
            "Temporary key is expected be in hexadecimal digits for a byte string."
        ) from exc
Esempio n. 11
0
def auth_error_common(status):
    """Checks if status code is 401 or 403 and raises appropriate exception."""
    if status == http.HTTPStatus.UNAUTHORIZED:
        raise AuthenticationError()
    elif status == http.HTTPStatus.FORBIDDEN:
        raise AccessDeniedError(message="Insufficient credentials")