-
Notifications
You must be signed in to change notification settings - Fork 108
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial pbench user authentication model implementation
This implements 5 basic user APIs 1. Register User Handles Pbench User registration via JSON request POST /v1/register 2. Login User: POST /v1/login Returns a valid pbench auth token User is allowed to issue multiple login requests and thus generating multiple auth tokens, Each token is stored in a active_tokens table and has its own expiry User is authenticated for subsequest API calls if token not expired and present in the active_tokens table 3. Logout user: POST /v1/logout Deletes the auth_token from the active_tokens table. Once logged out user can not use the same auth token for other API access. 4. Get User: GET /v1/user/<string:username> Returns the user's self information that was registered, the username must be provided in the url If the Auth header does not belong to the username, reject the request unless auth token belongs to the admin 5. Delete User: DELETE /v1/user/<string:username>" An API for a user to delete himself from the pbench database. A user can only perform delete action on himself unless the auth token belongs to the admin user. 6. Updare User: PUT /v1/user/<string:username> An API for updating the User registration fields, the username must be provided in the url Update is not allowed on registerd_on field
- Loading branch information
Showing
16 changed files
with
1,575 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import jwt | ||
import os | ||
import datetime | ||
from flask import request, abort | ||
from flask_httpauth import HTTPTokenAuth | ||
from pbench.server.database.models.users import User | ||
from pbench.server.database.models.active_tokens import ActiveTokens | ||
|
||
|
||
class Auth: | ||
token_auth = HTTPTokenAuth("Bearer") | ||
|
||
@staticmethod | ||
def set_logger(logger): | ||
# Logger gets set at the time of auth module initialization | ||
Auth.logger = logger | ||
|
||
def encode_auth_token(self, token_expire_duration, user_id): | ||
""" | ||
Generates the Auth Token | ||
:return: jwt token string | ||
""" | ||
current_utc = datetime.datetime.utcnow() | ||
payload = { | ||
"iat": current_utc, | ||
"exp": current_utc + datetime.timedelta(minutes=int(token_expire_duration)), | ||
"sub": user_id, | ||
} | ||
|
||
# Get jwt key | ||
jwt_key = self.get_secret_key() | ||
return jwt.encode(payload, jwt_key, algorithm="HS256") | ||
|
||
def get_secret_key(self): | ||
try: | ||
return os.getenv("SECRET_KEY", "my_precious") | ||
except Exception as e: | ||
Auth.logger.exception(f"{__name__}: ERROR: {e.__traceback__}") | ||
|
||
def verify_user(self, username): | ||
""" | ||
Check if the provided username belongs to the current user by | ||
querying the Usermodel with the current user | ||
:param username: | ||
:param logger | ||
:return: User (UserModel instance), verified status (boolean) | ||
""" | ||
user = User.query(id=self.token_auth.current_user().id) | ||
# check if the current username matches with the one provided | ||
verified = user is not None and user.username == username | ||
Auth.logger.warning("verified status of user '{}' is '{}'", username, verified) | ||
|
||
return user, verified | ||
|
||
def get_auth_token(self, logger): | ||
# get auth token | ||
auth_header = request.headers.get("Authorization") | ||
|
||
if not auth_header: | ||
logger.warning("Missing expected Authorization header") | ||
abort( | ||
403, | ||
message="Please add 'Authorization' token as Authorization: Bearer <session_token>", | ||
) | ||
|
||
try: | ||
auth_schema, auth_token = auth_header.split() | ||
except ValueError: | ||
logger.warning("Malformed Auth header") | ||
abort( | ||
401, | ||
message="Malformed Authorization header, please add request header as Authorization: Bearer <session_token>", | ||
) | ||
else: | ||
if auth_schema.lower() != "bearer": | ||
logger.warning( | ||
"Expected authorization schema to be 'bearer', not '{}'", | ||
auth_schema, | ||
) | ||
abort( | ||
401, | ||
message="Malformed Authorization header, request auth needs bearer token: Bearer <session_token>", | ||
) | ||
return auth_token | ||
|
||
@staticmethod | ||
@token_auth.verify_token | ||
def verify_auth(auth_token): | ||
""" | ||
Validates the auth token | ||
:param auth_token: | ||
:return: User object/None | ||
""" | ||
try: | ||
payload = jwt.decode( | ||
auth_token, os.getenv("SECRET_KEY", "my_precious"), algorithms="HS256", | ||
) | ||
user_id = payload["sub"] | ||
if ActiveTokens.valid(auth_token): | ||
user = User.query(id=user_id) | ||
return user | ||
except jwt.ExpiredSignatureError: | ||
try: | ||
ActiveTokens.delete(auth_token) | ||
except Exception: | ||
Auth.logger.error( | ||
"User attempted Pbench expired token but we could not delete the expired auth token from the database. token: '{}'", | ||
auth_token, | ||
) | ||
return None | ||
Auth.logger.warning( | ||
"User attempted Pbench expired token '{}', Token deleted from the database and no longer tracked", | ||
auth_token, | ||
) | ||
except jwt.InvalidTokenError: | ||
Auth.logger.warning("User attempted invalid Pbench token '{}'", auth_token) | ||
except Exception: | ||
Auth.logger.exception( | ||
"Exception occurred while verifying the auth token '{}'", auth_token | ||
) | ||
return None |
Oops, something went wrong.