user login stuff

can load / and /tasks (without permission stuff)
This commit is contained in:
mike cullerton 2022-07-29 11:27:28 -04:00
parent 6c6badc3f5
commit eb925ed2d6
6 changed files with 199 additions and 71 deletions

View File

@ -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:

View File

@ -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

View File

@ -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."""

View File

@ -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:

View File

@ -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
}

View File

@ -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