mirror of
https://github.com/sartography/spiffworkflow-backend.git
synced 2025-02-24 13:28:31 +00:00
user login stuff
can load / and /tasks (without permission stuff)
This commit is contained in:
parent
6c6badc3f5
commit
eb925ed2d6
@ -207,14 +207,14 @@ def process_model_run(process_model_id: str) -> Union[str, Response]:
|
||||
)
|
||||
|
||||
|
||||
def _find_or_create_user(username: str = "test_user1") -> Any:
|
||||
"""Find_or_create_user."""
|
||||
user = UserModel.query.filter_by(username=username).first()
|
||||
if user is None:
|
||||
user = UserModel(username=username)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return user
|
||||
# def _find_or_create_user(username: str = "test_user1") -> Any:
|
||||
# """Find_or_create_user."""
|
||||
# user = UserModel.query.filter_by(username=username).first()
|
||||
# if user is None:
|
||||
# user = UserModel(username=username)
|
||||
# db.session.add(user)
|
||||
# db.session.commit()
|
||||
# return user
|
||||
|
||||
|
||||
def _allowed_file(filename: str) -> bool:
|
||||
|
@ -20,6 +20,7 @@ from flask_bpmn.models.db import db
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.services.authentication_service import PublicAuthenticationService, get_keycloak_args
|
||||
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||
from spiffworkflow_backend.services.user_service import UserService
|
||||
|
||||
"""
|
||||
.. module:: crc.api.user
|
||||
@ -55,31 +56,45 @@ def verify_token(token: Optional[str] = None) -> Dict[str, Optional[str]]:
|
||||
# raise ApiError(code="fail_decode_auth_token",
|
||||
# message="Cannot decode the auth token")
|
||||
|
||||
try:
|
||||
user_info = AuthorizationService().get_user_info_from_id_token_object(token)
|
||||
except ApiError as ae:
|
||||
raise ae
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Exception raised in get_token: {e}")
|
||||
raise ApiError(code="fail_get_user_info",
|
||||
message="Cannot get user info from token")
|
||||
user_info = None
|
||||
token_type = get_token_type(token)
|
||||
if token_type == 'id_token' :
|
||||
try:
|
||||
user_info = AuthorizationService().get_user_info_from_id_token(token)
|
||||
except ApiError as ae:
|
||||
raise ae
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Exception raised in get_token: {e}")
|
||||
raise ApiError(code="fail_get_user_info",
|
||||
message="Cannot get user info from token")
|
||||
|
||||
if user_info and 'error' not in user_info: # not sure what to test yet
|
||||
user_model = UserModel.query.filter(UserModel.service == 'keycloak').filter(UserModel.service_id==user_info['sub']).first()
|
||||
user_model = UserModel.query\
|
||||
.filter(UserModel.service == 'keycloak')\
|
||||
.filter(UserModel.service_id==user_info['sub'])\
|
||||
.first()
|
||||
if user_model is None:
|
||||
user_model = UserModel(service='keycloak',
|
||||
service_id=user_info['sub'],
|
||||
name=user_info['preferred_username'],
|
||||
username=user_info['sub'])
|
||||
db.session.add(user_model)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Exception raised while adding user in get_token: {e}")
|
||||
raise ApiError(code="fail_add_user_model",
|
||||
message="Cannot add user in verify_token") from e
|
||||
user_model = UserService().create_user(service='keycloak',
|
||||
service_id=user_info['sub'],
|
||||
name=user_info['name'],
|
||||
username=user_info['preferred_username'],
|
||||
email=user_info['email'])
|
||||
# user_model = UserModel.query\
|
||||
# .filter(UserModel.id == user['id'])\
|
||||
# .first()
|
||||
# user_model = UserModel(service='keycloak',
|
||||
# service_id=user_info['sub'],
|
||||
# name=user_info['preferred_username'],
|
||||
# username=user_info['sub'])
|
||||
# db.session.add(user_model)
|
||||
# try:
|
||||
# db.session.commit()
|
||||
# except Exception as e:
|
||||
# current_app.logger.error(f"Exception raised while adding user in get_token: {e}")
|
||||
# raise ApiError(code="fail_add_user_model",
|
||||
# message="Cannot add user in verify_token") from e
|
||||
if user_model:
|
||||
g.user = user_model.id
|
||||
g.user = user_model
|
||||
|
||||
# If the user is valid, store the token for this session
|
||||
if g.user:
|
||||
@ -87,7 +102,11 @@ def verify_token(token: Optional[str] = None) -> Dict[str, Optional[str]]:
|
||||
# What should we return? Dict?
|
||||
# return user_info
|
||||
# TODO: Need to return dictionary containing 'scope'
|
||||
return validate_scope(token, user_info, user_model)
|
||||
scope = get_scope(token)
|
||||
return {'uid': g.user.id,
|
||||
'sub': g.user.id,
|
||||
'scope': scope}
|
||||
# return validate_scope(token, user_info, user_model)
|
||||
else:
|
||||
raise ApiError(code="no_user_id",
|
||||
message="Cannot get a user id")
|
||||
@ -116,7 +135,7 @@ def verify_token(token: Optional[str] = None) -> Dict[str, Optional[str]]:
|
||||
message="No authorization token was available.",
|
||||
status_code=401)
|
||||
|
||||
def validate_scope(token, user_info, user_model):
|
||||
def validate_scope(token) -> bool:
|
||||
print("validate_scope")
|
||||
# token = AuthorizationService().refresh_token(token)
|
||||
# user_info = AuthorizationService().get_user_info_from_public_access_token(token)
|
||||
@ -126,7 +145,6 @@ def validate_scope(token, user_info, user_model):
|
||||
# introspection = AuthorizationService().introspect_token(basic_token)
|
||||
return True
|
||||
|
||||
|
||||
def api_login(uid, password, redirect_url=None):
|
||||
|
||||
token = PublicAuthenticationService().get_public_access_token(uid, password)
|
||||
@ -166,7 +184,7 @@ def login_return(code, state, session_state):
|
||||
id_token = id_token_object['id_token']
|
||||
|
||||
if PublicAuthenticationService.validate_id_token(id_token):
|
||||
user_info = AuthorizationService().get_user_info_from_id_token_object(id_token_object['access_token'])
|
||||
user_info = AuthorizationService().get_user_info_from_id_token(id_token_object['access_token'])
|
||||
if user_info and 'error' not in user_info:
|
||||
user_model = UserModel.query.filter(UserModel.service == 'keycloak').filter(UserModel.service_id==user_info['sub']).first()
|
||||
if user_model is None:
|
||||
@ -194,7 +212,30 @@ def logout(id_token: str, redirect_url: str | None):
|
||||
def logout_return():
|
||||
return redirect(f"http://localhost:7001/")
|
||||
|
||||
def is_internal_token(token) -> bool:
|
||||
decoded_token = UserModel.decode_auth_token(token)
|
||||
if 'token_type' in decoded_token and 'sub' in decoded_token:
|
||||
return True
|
||||
def get_token_type(token) -> bool:
|
||||
token_type = None
|
||||
try:
|
||||
PublicAuthenticationService.validate_id_token(token)
|
||||
except ApiError as ae:
|
||||
if ae.status_code == 401:
|
||||
raise ae
|
||||
print(f"ApiError in get_token_type: {ae}")
|
||||
except Exception as e:
|
||||
print(f"Exception in get_token_type: {e}")
|
||||
else:
|
||||
token_type = 'id_token'
|
||||
# try:
|
||||
# # see if we have an open_id token
|
||||
# decoded_token = AuthorizationService.decode_auth_token(token)
|
||||
# else:
|
||||
# if 'sub' in decoded_token and 'iss' in decoded_token and 'aud' in decoded_token:
|
||||
# token_type = 'id_token'
|
||||
|
||||
# if 'token_type' in decoded_token and 'sub' in decoded_token:
|
||||
# return True
|
||||
return token_type
|
||||
|
||||
def get_scope(token):
|
||||
decoded_token = jwt.decode(token, options={"verify_signature": False})
|
||||
scope = decoded_token['scope']
|
||||
return scope
|
||||
|
@ -5,6 +5,7 @@ from typing import Final
|
||||
|
||||
import flask.wrappers
|
||||
from flask import Blueprint
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import Response
|
||||
from flask_bpmn.api.api_error import ApiError
|
||||
@ -12,6 +13,7 @@ from flask_bpmn.models.db import db
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from spiffworkflow_backend.models.group import GroupModel
|
||||
from spiffworkflow_backend.models.principal import PrincipalModel
|
||||
from spiffworkflow_backend.models.user import UserModel
|
||||
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
|
||||
|
||||
@ -20,31 +22,47 @@ APPLICATION_JSON: Final = "application/json"
|
||||
user_blueprint = Blueprint("main", __name__)
|
||||
|
||||
|
||||
@user_blueprint.route("/user/<username>", methods=["GET"])
|
||||
def create_user(username: str) -> flask.wrappers.Response:
|
||||
"""Create_user."""
|
||||
user = UserModel.query.filter_by(username=username).first()
|
||||
if user is not None:
|
||||
raise (
|
||||
ApiError(
|
||||
code="user_already_exists",
|
||||
message=f"User already exists: {username}",
|
||||
status_code=409,
|
||||
)
|
||||
)
|
||||
|
||||
user = UserModel(username=username)
|
||||
try:
|
||||
db.session.add(user)
|
||||
except IntegrityError as exception:
|
||||
raise (
|
||||
ApiError(code="integrity_error", message=repr(exception), status_code=500)
|
||||
) from exception
|
||||
|
||||
db.session.commit()
|
||||
return Response(json.dumps({"id": user.id}), status=201, mimetype=APPLICATION_JSON)
|
||||
|
||||
# @user_blueprint.route("/user/<username>", methods=["GET"])
|
||||
# def create_user(username: str) -> flask.wrappers.Response:
|
||||
# """Create_user."""
|
||||
# user = UserService.create_user('internal', username)
|
||||
# return Response(json.dumps({"id": user.id}), status=201, mimetype=APPLICATION_JSON)
|
||||
|
||||
# def _create_user(username):
|
||||
# user = UserModel.query.filter_by(username=username).first()
|
||||
# if user is not None:
|
||||
# raise (
|
||||
# ApiError(
|
||||
# code="user_already_exists",
|
||||
# message=f"User already exists: {username}",
|
||||
# status_code=409,
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# user = UserModel(username=username,
|
||||
# service='internal',
|
||||
# service_id=username,
|
||||
# name=username)
|
||||
# try:
|
||||
# db.session.add(user)
|
||||
# except IntegrityError as exception:
|
||||
# raise (
|
||||
# ApiError(code="integrity_error", message=repr(exception), status_code=500)
|
||||
# ) from exception
|
||||
#
|
||||
# try:
|
||||
# db.session.commit()
|
||||
# except Exception as e:
|
||||
# db.session.rollback()
|
||||
# raise ApiError(code='add_user_error',
|
||||
# message=f'Could not add user {username}') from e
|
||||
# try:
|
||||
# create_principal(user.id)
|
||||
# except ApiError as ae:
|
||||
# # TODO: What is the right way to do this
|
||||
# raise ae
|
||||
# return user
|
||||
#
|
||||
@user_blueprint.route("/user/<username>", methods=["DELETE"])
|
||||
def delete_user(username: str) -> flask.wrappers.Response:
|
||||
"""Delete_user."""
|
||||
|
@ -101,14 +101,22 @@ class PublicAuthenticationService:
|
||||
status_code=401)
|
||||
try:
|
||||
assert decoded_token['iss'] == f"{keycloak_server_url}/realms/{keycloak_realm_name}"
|
||||
assert decoded_token['aud'] == keycloak_client_id
|
||||
assert keycloak_client_id in decoded_token['aud'] or 'account' in decoded_token['aud']
|
||||
if 'azp' in decoded_token:
|
||||
assert decoded_token['azp'] == keycloak_client_id
|
||||
# TODO: not sure why this isn't keycloak_client_id
|
||||
assert decoded_token['azp'] in keycloak_client_id, 'account'
|
||||
assert now > decoded_token['iat']
|
||||
assert now < decoded_token['exp']
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Exception validating id_token: {e}")
|
||||
return False
|
||||
|
||||
try:
|
||||
assert now < decoded_token['exp']
|
||||
except:
|
||||
raise ApiError(code='invalid_token',
|
||||
message="Your token is expired. Please Login",
|
||||
status_code=401)
|
||||
|
||||
return True
|
||||
|
||||
def get_public_access_token(self, username, password) -> dict:
|
||||
|
@ -22,7 +22,7 @@ class AuthorizationService:
|
||||
keycloak_client_secret_key = current_app.config["KEYCLOAK_CLIENT_SECRET_KEY"] # noqa: S105
|
||||
return keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key
|
||||
|
||||
def get_user_info_from_id_token_object(self, id_token_object):
|
||||
def get_user_info_from_id_token(self, token):
|
||||
"""This seems to work with basic tokens too"""
|
||||
json_data = None
|
||||
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args()
|
||||
@ -44,11 +44,12 @@ class AuthorizationService:
|
||||
# else:
|
||||
# auth_bearer_string = f"Bearer {id_token_object['access_token']}"
|
||||
# auth_bearer_string = f"Basic {keycloak_client_secret_key}"
|
||||
headers = {"Authorization": f"Bearer {id_token_object}"}
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
data = {'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
|
||||
'client_id': keycloak_client_id,
|
||||
# "subject_token": id_token_object['access_token'],
|
||||
"subject_token": id_token_object,
|
||||
"subject_token": token,
|
||||
"audience": keycloak_client_id}
|
||||
|
||||
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/userinfo"
|
||||
@ -56,9 +57,12 @@ class AuthorizationService:
|
||||
request_response = requests.get(request_url, headers=headers)
|
||||
except Exception as e:
|
||||
print(f"get_user_from_token: Exception: {e}")
|
||||
else:
|
||||
print("else")
|
||||
if request_response.status_code == 200:
|
||||
json_data = json.loads(request_response.text)
|
||||
elif request_response.status_code == 401:
|
||||
raise ApiError(code="invalid_token",
|
||||
message="Please login",
|
||||
status_code=401)
|
||||
return json_data
|
||||
|
||||
def refresh_token(self, token):
|
||||
@ -190,7 +194,7 @@ class AuthorizationService:
|
||||
auth_status = json.loads(auth_response.text)
|
||||
return auth_status
|
||||
|
||||
def get_permissions_by_token_for_resource_and_scope(self, basic_token, resource, scope):
|
||||
def get_permissions_by_token_for_resource_and_scope(self, basic_token, resource=None, scope=None):
|
||||
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args()
|
||||
|
||||
# basic_token = AuthorizationService().refresh_token(basic_token)
|
||||
@ -200,11 +204,16 @@ class AuthorizationService:
|
||||
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Authorization": auth_bearer_string}
|
||||
permision = ''
|
||||
if resource:
|
||||
permision += resource
|
||||
if scope:
|
||||
permision += '#' + resource
|
||||
data = {'client_id': keycloak_client_id,
|
||||
'client_secret': keycloak_client_secret_key,
|
||||
"grant_type": 'urn:ietf:params:oauth:grant-type:uma-ticket',
|
||||
"response_mode": "permissions",
|
||||
"permission": f"{resource}#{scope}",
|
||||
"permission": permision,
|
||||
"audience": keycloak_client_id,
|
||||
"response_include_resource_name": True
|
||||
}
|
||||
|
@ -14,6 +14,44 @@ from spiffworkflow_backend.models.user import UserModel
|
||||
class UserService:
|
||||
"""Provides common tools for working with users."""
|
||||
|
||||
def create_user(self, service, service_id, name=None, username=None, email=None):
|
||||
user = UserModel.query.filter(UserModel.service == service)\
|
||||
.filter(UserModel.service_id == service_id)\
|
||||
.first()
|
||||
if user is not None:
|
||||
raise (
|
||||
ApiError(
|
||||
code="user_already_exists",
|
||||
message=f"User already exists: {username}",
|
||||
status_code=409,
|
||||
)
|
||||
)
|
||||
|
||||
user = UserModel(username=username,
|
||||
service=service,
|
||||
service_id=service_id,
|
||||
name=name,
|
||||
email=email)
|
||||
try:
|
||||
db.session.add(user)
|
||||
except IntegrityError as exception:
|
||||
raise (
|
||||
ApiError(code="integrity_error", message=repr(exception), status_code=500)
|
||||
) from exception
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
raise ApiError(code='add_user_error',
|
||||
message=f'Could not add user {username}') from e
|
||||
try:
|
||||
self.create_principal(user.id)
|
||||
except ApiError as ae:
|
||||
# TODO: What is the right way to do this
|
||||
raise ae
|
||||
return user
|
||||
|
||||
# Returns true if the current user is logged in.
|
||||
@staticmethod
|
||||
def has_user() -> bool:
|
||||
@ -187,3 +225,17 @@ class UserService:
|
||||
code="no_principal_found",
|
||||
message=f"No principal was found for user_id: {user_id}",
|
||||
)
|
||||
|
||||
def create_principal(self, user_id):
|
||||
principal = PrincipalModel.query.filter_by(user_id=user_id).first()
|
||||
if principal is None:
|
||||
principal = PrincipalModel(user_id=user_id)
|
||||
db.session.add(principal)
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error(f"Exception in create_principal: {e}")
|
||||
raise ApiError(code="add_principal_error",
|
||||
message=f"Could not create principal {user_id}") from e
|
||||
return principal
|
||||
|
Loading…
x
Reference in New Issue
Block a user