ran precommit stuff

This commit is contained in:
jasquat 2022-07-29 13:39:57 -04:00
parent fea30d0a16
commit 25c6e74a90
13 changed files with 644 additions and 432 deletions

View File

@ -24,7 +24,7 @@ paths:
tags: tags:
- Authentication - Authentication
responses: responses:
'200': "200":
description: Redirects to authentication server description: Redirects to authentication server
/login_return: /login_return:
parameters: parameters:
@ -49,7 +49,7 @@ paths:
tags: tags:
- Authentication - Authentication
responses: responses:
'200': "200":
description: Test Return Response description: Test Return Response
/logout: /logout:
parameters: parameters:
@ -70,7 +70,7 @@ paths:
tags: tags:
- Authentication - Authentication
responses: responses:
'200': "200":
description: Logout Authenticated User description: Logout Authenticated User
/logout_return: /logout_return:
get: get:
@ -80,7 +80,7 @@ paths:
tags: tags:
- Authentication - Authentication
responses: responses:
'200': "200":
description: Logout Authenticated User description: Logout Authenticated User
/login_swagger: /login_swagger:
@ -110,7 +110,7 @@ paths:
tags: tags:
- Authentication - Authentication
responses: responses:
'304': "304":
description: Redirection to the hosted frontend with an auth_token header. description: Redirection to the hosted frontend with an auth_token header.
/status: /status:

View File

@ -14,7 +14,11 @@ CORS_ALLOW_ORIGINS = re.split(
) )
# Keycloak server # Keycloak server
KEYCLOAK_SERVER_URL = environ.get("KEYCLOAK_SERVER_URL", default="http://localhost:7002") KEYCLOAK_SERVER_URL = environ.get(
"KEYCLOAK_SERVER_URL", default="http://localhost:7002"
)
KEYCLOAK_CLIENT_ID = environ.get("KEYCLOAK_CLIENT_ID", default="spiffworkflow-backend") KEYCLOAK_CLIENT_ID = environ.get("KEYCLOAK_CLIENT_ID", default="spiffworkflow-backend")
KEYCLOAK_REALM_NAME = environ.get("KEYCLOAK_REALM_NAME", default="spiffworkflow") KEYCLOAK_REALM_NAME = environ.get("KEYCLOAK_REALM_NAME", default="spiffworkflow")
KEYCLOAK_CLIENT_SECRET_KEY = environ.get("KEYCLOAK_CLIENT_SECRET_KEY", default="JXeQExm0JhQPLumgHtIIqf52bDalHz0q") # noqa: S105 KEYCLOAK_CLIENT_SECRET_KEY = environ.get(
"KEYCLOAK_CLIENT_SECRET_KEY", default="JXeQExm0JhQPLumgHtIIqf52bDalHz0q"
) # noqa: S105

View File

@ -12,9 +12,7 @@ def find_or_create_user(username: str = "test_user1") -> Any:
"""Find_or_create_user.""" """Find_or_create_user."""
user = UserModel.query.filter_by(username=username).first() user = UserModel.query.filter_by(username=username).first()
if user is None: if user is None:
user = UserModel(username=username, user = UserModel(username=username, service="local", service_id=username)
service='local',
service_id=username)
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()

View File

@ -1,10 +1,7 @@
"""User.""" """User."""
from typing import Union
import jwt import jwt
import marshmallow import marshmallow
from flask import current_app from flask import current_app
from flask_bpmn.api.api_error import ApiError
from flask_bpmn.models.db import db from flask_bpmn.models.db import db
from flask_bpmn.models.db import SpiffworkflowBaseDBModel from flask_bpmn.models.db import SpiffworkflowBaseDBModel
from marshmallow import Schema from marshmallow import Schema
@ -18,11 +15,7 @@ class UserModel(SpiffworkflowBaseDBModel):
"""UserModel.""" """UserModel."""
__tablename__ = "user" __tablename__ = "user"
__table_args__ = ( __table_args__ = (db.UniqueConstraint("service", "service_id", name="service_key"),)
db.UniqueConstraint(
"service", "service_id", name="service_key"
),
)
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), nullable=False, unique=True) username = db.Column(db.String(50), nullable=False, unique=True)
uid = db.Column(db.String(50), unique=True) uid = db.Column(db.String(50), unique=True)
@ -57,7 +50,7 @@ class UserModel(SpiffworkflowBaseDBModel):
# 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=hours, minutes=0, seconds=0), # 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=hours, minutes=0, seconds=0),
# 'iat': datetime.datetime.utcnow(), # 'iat': datetime.datetime.utcnow(),
"sub": f"service:{self.service}::service_id:{self.service_id}", "sub": f"service:{self.service}::service_id:{self.service_id}",
"token_type": "internal" "token_type": "internal",
} }
return jwt.encode( return jwt.encode(
payload, payload,
@ -71,11 +64,12 @@ class UserModel(SpiffworkflowBaseDBModel):
@classmethod @classmethod
def from_open_id_user_info(cls, user_info): def from_open_id_user_info(cls, user_info):
"""From_open_id_user_info."""
instance = cls() instance = cls()
instance.service = 'keycloak' instance.service = "keycloak"
instance.service_id = user_info['sub'] instance.service_id = user_info["sub"]
instance.name = user_info['preferred_username'] instance.name = user_info["preferred_username"]
instance.username = user_info['sub'] instance.username = user_info["sub"]
return instance return instance

View File

@ -1,22 +1,14 @@
"""APIs for dealing with process groups, process models, and process instances.""" """APIs for dealing with process groups, process models, and process instances."""
from typing import Any
from typing import Union from typing import Union
from flask import Blueprint from flask import Blueprint
from flask import current_app
from flask import flash from flask import flash
from flask import redirect from flask import redirect
from flask import render_template from flask import render_template
from flask import request from flask import request
from flask import url_for from flask import url_for
from flask_bpmn.models.db import db
from werkzeug.wrappers.response import Response from werkzeug.wrappers.response import Response
from spiffworkflow_backend.models.principal import PrincipalModel
from spiffworkflow_backend.models.process_instance_report import (
ProcessInstanceReportModel,
)
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
ProcessInstanceProcessor, ProcessInstanceProcessor,
) )

View File

@ -1,24 +1,18 @@
"""User.""" """User."""
import jwt
import requests
import json
from urllib.parse import urlencode, quote
import base64 import base64
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
from flask import g import jwt
from flask import current_app from flask import current_app
from flask import g
from flask import redirect from flask import redirect
from flask import request
from flask.app import Flask
from flask_bpmn.api.api_error import ApiError from flask_bpmn.api.api_error import ApiError
from flask_bpmn.models.db import db
from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.authentication_service import PublicAuthenticationService, get_keycloak_args from spiffworkflow_backend.services.authentication_service import (
PublicAuthenticationService,
)
from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.user_service import UserService from spiffworkflow_backend.services.user_service import UserService
@ -27,6 +21,7 @@ from spiffworkflow_backend.services.user_service import UserService
:synopsis: Single Sign On (SSO) user login and session handlers :synopsis: Single Sign On (SSO) user login and session handlers
""" """
def verify_token(token: Optional[str] = None) -> Dict[str, Optional[str]]: def verify_token(token: Optional[str] = None) -> Dict[str, Optional[str]]:
"""Verify the token for the user (if provided). """Verify the token for the user (if provided).
@ -42,34 +37,37 @@ def verify_token(token: Optional[str] = None) -> Dict[str, Optional[str]]:
ApiError: If not on production and token is not valid, returns an 'invalid_token' 403 error. ApiError: If not on production and token is not valid, returns an 'invalid_token' 403 error.
If on production and user is not authenticated, returns a 'no_user' 403 error. If on production and user is not authenticated, returns a 'no_user' 403 error.
""" """
if token: if token:
user_info = None user_info = None
token_type = get_token_type(token) token_type = get_token_type(token)
if token_type == 'id_token' : if token_type == "id_token":
try: try:
user_info = AuthorizationService().get_user_info_from_id_token(token) user_info = AuthorizationService().get_user_info_from_id_token(token)
except ApiError as ae: except ApiError as ae:
raise ae raise ae
except Exception as e: except Exception as e:
current_app.logger.error(f"Exception raised in get_token: {e}") current_app.logger.error(f"Exception raised in get_token: {e}")
raise ApiError(code="fail_get_user_info", raise ApiError(
message="Cannot get user info from token") 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 if user_info and "error" not in user_info: # not sure what to test yet
user_model = UserModel.query\ user_model = (
.filter(UserModel.service == 'keycloak')\ UserModel.query.filter(UserModel.service == "keycloak")
.filter(UserModel.service_id==user_info['sub'])\ .filter(UserModel.service_id == user_info["sub"])
.first() .first()
)
if user_model is None: if user_model is None:
# Do we ever get here any more, now that we have login_return method? # Do we ever get here any more, now that we have login_return method?
current_app.logger.debug("create_user in verify_token") current_app.logger.debug("create_user in verify_token")
user_model = UserService().create_user(service='keycloak', user_model = UserService().create_user(
service_id=user_info['sub'], service="keycloak",
name=user_info['name'], service_id=user_info["sub"],
username=user_info['preferred_username'], name=user_info["name"],
email=user_info['email']) username=user_info["preferred_username"],
email=user_info["email"],
)
if user_model: if user_model:
g.user = user_model g.user = user_model
@ -77,22 +75,18 @@ def verify_token(token: Optional[str] = None) -> Dict[str, Optional[str]]:
if g.user: if g.user:
g.token = token g.token = token
scope = get_scope(token) scope = get_scope(token)
return {'uid': g.user.id, return {"uid": g.user.id, "sub": g.user.id, "scope": scope}
'sub': g.user.id,
'scope': scope}
# return validate_scope(token, user_info, user_model) # return validate_scope(token, user_info, user_model)
else: else:
raise ApiError(code="no_user_id", raise ApiError(code="no_user_id", message="Cannot get a user id")
message="Cannot get a user id")
# no user_info # no user_info
else: else:
raise ApiError(code="no_user_info", raise ApiError(code="no_user_info", message="Cannot retrieve user info")
message="Cannot retrieve user info")
# no token -- do we ever get here? # no token -- do we ever get here?
else: else:
if app.config.get('DEVELOPMENT'): if app.config.get("DEVELOPMENT"):
# Fall back to a default user if this is not production. # Fall back to a default user if this is not production.
g.user = UserModel.query.first() g.user = UserModel.query.first()
if not g.user: if not g.user:
@ -105,11 +99,15 @@ def verify_token(token: Optional[str] = None) -> Dict[str, Optional[str]]:
return token_info return token_info
else: else:
raise ApiError(code="no_auth_token", raise ApiError(
code="no_auth_token",
message="No authorization token was available.", message="No authorization token was available.",
status_code=401) status_code=401,
)
def validate_scope(token) -> bool: def validate_scope(token) -> bool:
"""Validate_scope."""
print("validate_scope") print("validate_scope")
# token = AuthorizationService().refresh_token(token) # token = AuthorizationService().refresh_token(token)
# user_info = AuthorizationService().get_user_info_from_public_access_token(token) # user_info = AuthorizationService().get_user_info_from_public_access_token(token)
@ -119,80 +117,101 @@ def validate_scope(token) -> bool:
# introspection = AuthorizationService().introspect_token(basic_token) # introspection = AuthorizationService().introspect_token(basic_token)
return True return True
def api_login(uid, password, redirect_url=None):
def api_login(uid, password, redirect_url=None):
"""Api_login."""
token = PublicAuthenticationService().get_public_access_token(uid, password) token = PublicAuthenticationService().get_public_access_token(uid, password)
g.token = token g.token = token
return token return token
def encode_auth_token(uid): def encode_auth_token(uid):
""" """
Generates the Auth Token Generates the Auth Token
:return: string :return: string
""" """
payload = { payload = {"sub": uid}
'sub': uid
}
return jwt.encode( return jwt.encode(
payload, payload,
app.config.get('SECRET_KEY'), app.config.get("SECRET_KEY"),
algorithm='HS256', algorithm="HS256",
) )
def login(redirect_url='/'):
def login(redirect_url="/"):
"""Login."""
state = PublicAuthenticationService.generate_state(redirect_url) state = PublicAuthenticationService.generate_state(redirect_url)
login_redirect_url = PublicAuthenticationService().get_login_redirect_url(state) login_redirect_url = PublicAuthenticationService().get_login_redirect_url(state)
return redirect(login_redirect_url) return redirect(login_redirect_url)
def login_return(code, state, session_state): def login_return(code, state, session_state):
"""""" """"""
# TODO: Why does state look like this? # TODO: Why does state look like this?
# 'b\'eydyZWRpcmVjdF91cmwnOiAnaHR0cDovL2xvY2FsaG9zdDo3MDAxLyd9\'' # 'b\'eydyZWRpcmVjdF91cmwnOiAnaHR0cDovL2xvY2FsaG9zdDo3MDAxLyd9\''
# It has an extra 'b at the beginning and an extra ' at the end, # It has an extra 'b at the beginning and an extra ' at the end,
# so we use state[2:-1] # so we use state[2:-1]
state_dict = eval(base64.b64decode(state[2:-1]).decode('utf-8')) state_dict = eval(base64.b64decode(state[2:-1]).decode("utf-8"))
state_redirect_url = state_dict['redirect_url'] state_redirect_url = state_dict["redirect_url"]
id_token_object = PublicAuthenticationService().get_id_token_object(code) id_token_object = PublicAuthenticationService().get_id_token_object(code)
id_token = id_token_object['id_token'] id_token = id_token_object["id_token"]
if PublicAuthenticationService.validate_id_token(id_token): if PublicAuthenticationService.validate_id_token(id_token):
user_info = AuthorizationService().get_user_info_from_id_token(id_token_object['access_token']) user_info = AuthorizationService().get_user_info_from_id_token(
if user_info and 'error' not in user_info: id_token_object["access_token"]
user_model = UserModel.query.filter(UserModel.service == 'keycloak').filter(UserModel.service_id==user_info['sub']).first() )
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: if user_model is None:
current_app.logger.debug("create_user in login_return") current_app.logger.debug("create_user in login_return")
name = username = email = '' name = username = email = ""
if 'name' in user_info: if "name" in user_info:
name = user_info['name'] name = user_info["name"]
if 'username' in user_info: if "username" in user_info:
username = user_info['username'] username = user_info["username"]
if 'email' in user_info: if "email" in user_info:
email = user_info['email'] email = user_info["email"]
user_model = UserService().create_user(service='keycloak', user_model = UserService().create_user(
service_id=user_info['sub'], service="keycloak",
service_id=user_info["sub"],
name=name, name=name,
username=username, username=username,
email=email) email=email,
)
if user_model: if user_model:
g.user = user_model.id g.user = user_model.id
redirect_url = f"{state_redirect_url}?" + \ redirect_url = (
f"access_token={id_token_object['access_token']}&" + \ f"{state_redirect_url}?"
f"id_token={id_token}" + f"access_token={id_token_object['access_token']}&"
+ f"id_token={id_token}"
)
return redirect(redirect_url) return redirect(redirect_url)
# return f"{code} {state} {id_token}" # return f"{code} {state} {id_token}"
def logout(id_token: str, redirect_url: str | None): def logout(id_token: str, redirect_url: str | None):
return PublicAuthenticationService().logout(id_token=id_token, redirect_url=redirect_url) """Logout."""
return PublicAuthenticationService().logout(
id_token=id_token, redirect_url=redirect_url
)
def logout_return(): def logout_return():
"""Logout_return."""
return redirect(f"http://localhost:7001/") return redirect(f"http://localhost:7001/")
def get_token_type(token) -> bool: def get_token_type(token) -> bool:
"""Get_token_type."""
token_type = None token_type = None
try: try:
PublicAuthenticationService.validate_id_token(token) PublicAuthenticationService.validate_id_token(token)
@ -203,7 +222,7 @@ def get_token_type(token) -> bool:
except Exception as e: except Exception as e:
print(f"Exception in get_token_type: {e}") print(f"Exception in get_token_type: {e}")
else: else:
token_type = 'id_token' token_type = "id_token"
# try: # try:
# # see if we have an open_id token # # see if we have an open_id token
# decoded_token = AuthorizationService.decode_auth_token(token) # decoded_token = AuthorizationService.decode_auth_token(token)
@ -215,7 +234,9 @@ def get_token_type(token) -> bool:
# return True # return True
return token_type return token_type
def get_scope(token): def get_scope(token):
"""Get_scope."""
decoded_token = jwt.decode(token, options={"verify_signature": False}) decoded_token = jwt.decode(token, options={"verify_signature": False})
scope = decoded_token['scope'] scope = decoded_token["scope"]
return scope return scope

View File

@ -5,7 +5,6 @@ from typing import Final
import flask.wrappers import flask.wrappers
from flask import Blueprint from flask import Blueprint
from flask import current_app
from flask import request from flask import request
from flask import Response from flask import Response
from flask_bpmn.api.api_error import ApiError from flask_bpmn.api.api_error import ApiError
@ -13,7 +12,6 @@ from flask_bpmn.models.db import db
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from spiffworkflow_backend.models.group import GroupModel 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 import UserModel
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel

View File

@ -1,37 +1,42 @@
"""Authentication_service.""" """Authentication_service."""
import base64
import enum
import json
import time
from typing import Optional from typing import Optional
import requests
import base64
import json
import enum
import random
import jwt import jwt
import time import requests
from flask import g
from flask import current_app from flask import current_app
from flask import redirect from flask import redirect
from flask_bpmn.api.api_error import ApiError from flask_bpmn.api.api_error import ApiError
from flask_bpmn.models.db import db
from keycloak import KeycloakOpenID # type: ignore from keycloak import KeycloakOpenID # type: ignore
from keycloak.uma_permissions import AuthStatus # type: ignore from keycloak.uma_permissions import AuthStatus # type: ignore
from keycloak import KeycloakAdmin
from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import AuthorizationService
def get_keycloak_args(): def get_keycloak_args():
keycloak_server_url = current_app.config['KEYCLOAK_SERVER_URL'] """Get_keycloak_args."""
keycloak_server_url = current_app.config["KEYCLOAK_SERVER_URL"]
keycloak_client_id = current_app.config["KEYCLOAK_CLIENT_ID"] keycloak_client_id = current_app.config["KEYCLOAK_CLIENT_ID"]
keycloak_realm_name = current_app.config["KEYCLOAK_REALM_NAME"] keycloak_realm_name = current_app.config["KEYCLOAK_REALM_NAME"]
keycloak_client_secret_key = current_app.config["KEYCLOAK_CLIENT_SECRET_KEY"] # noqa: S105 keycloak_client_secret_key = current_app.config[
return keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key "KEYCLOAK_CLIENT_SECRET_KEY"
] # noqa: S105
return (
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
)
class AuthenticationServiceProviders(enum.Enum): class AuthenticationServiceProviders(enum.Enum):
keycloak = 'keycloak' """AuthenticationServiceProviders."""
internal = 'internal'
keycloak = "keycloak"
internal = "internal"
class PublicAuthenticationService: class PublicAuthenticationService:
@ -39,46 +44,72 @@ class PublicAuthenticationService:
It uses a separate public keycloak client: spiffworkflow-frontend It uses a separate public keycloak client: spiffworkflow-frontend
Used during development to make testing easy. Used during development to make testing easy.
""" """
def logout(self, redirect_url: str='/', id_token: str | None=None):
if id_token is None:
raise ApiError(code='missing_id_token',
message="id_token is missing",
status_code=400)
return_redirect_url = 'http://localhost:7000/v1.0/logout_return' def logout(self, redirect_url: str = "/", id_token: str | None = None):
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = get_keycloak_args() """Logout."""
if id_token is None:
raise ApiError(
code="missing_id_token", message="id_token is missing", status_code=400
)
return_redirect_url = "http://localhost:7000/v1.0/logout_return"
(
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = get_keycloak_args()
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/logout?post_logout_redirect_uri={return_redirect_url}&id_token_hint={id_token}" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/logout?post_logout_redirect_uri={return_redirect_url}&id_token_hint={id_token}"
return redirect(request_url) return redirect(request_url)
@staticmethod @staticmethod
def generate_state(redirect_url): def generate_state(redirect_url):
state = base64.b64encode(bytes(str({'redirect_url': redirect_url}), 'UTF-8')) """Generate_state."""
state = base64.b64encode(bytes(str({"redirect_url": redirect_url}), "UTF-8"))
return state return state
def get_login_redirect_url(self, state): def get_login_redirect_url(self, state):
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = get_keycloak_args() """Get_login_redirect_url."""
return_redirect_url = 'http://localhost:7000/v1.0/login_return' (
login_redirect_url = f'{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/auth?' + \ keycloak_server_url,
f'state={state}&' + \ keycloak_client_id,
'response_type=code&' + \ keycloak_realm_name,
f'client_id={keycloak_client_id}&' + \ keycloak_client_secret_key,
'scope=openid&' + \ ) = get_keycloak_args()
f'redirect_uri={return_redirect_url}' return_redirect_url = "http://localhost:7000/v1.0/login_return"
login_redirect_url = (
f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/auth?"
+ f"state={state}&"
+ "response_type=code&"
+ f"client_id={keycloak_client_id}&"
+ "scope=openid&"
+ f"redirect_uri={return_redirect_url}"
)
return login_redirect_url return login_redirect_url
def get_id_token_object(self, code): def get_id_token_object(self, code):
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = get_keycloak_args() """Get_id_token_object."""
(
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = get_keycloak_args()
BACKEND_BASIC_AUTH_STRING = f"{keycloak_client_id}:{keycloak_client_secret_key}" BACKEND_BASIC_AUTH_STRING = f"{keycloak_client_id}:{keycloak_client_secret_key}"
BACKEND_BASIC_AUTH_BYTES = bytes(BACKEND_BASIC_AUTH_STRING, encoding='ascii') BACKEND_BASIC_AUTH_BYTES = bytes(BACKEND_BASIC_AUTH_STRING, encoding="ascii")
BACKEND_BASIC_AUTH = base64.b64encode(BACKEND_BASIC_AUTH_BYTES) BACKEND_BASIC_AUTH = base64.b64encode(BACKEND_BASIC_AUTH_BYTES)
headers = {"Content-Type": "application/x-www-form-urlencoded", headers = {
"Authorization": f"Basic {BACKEND_BASIC_AUTH.decode('utf-8')}"} "Content-Type": "application/x-www-form-urlencoded",
"Authorization": f"Basic {BACKEND_BASIC_AUTH.decode('utf-8')}",
}
data = {'grant_type': 'authorization_code', data = {
'code': code, "grant_type": "authorization_code",
'redirect_uri': 'http://localhost:7000/v1.0/login_return'} "code": code,
"redirect_uri": "http://localhost:7000/v1.0/login_return",
}
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token"
@ -92,58 +123,80 @@ class PublicAuthenticationService:
https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
""" """
now = time.time() now = time.time()
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = get_keycloak_args() (
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = get_keycloak_args()
try: try:
decoded_token = jwt.decode(id_token, options={"verify_signature": False}) decoded_token = jwt.decode(id_token, options={"verify_signature": False})
except Exception as e: except Exception:
raise ApiError(code='bad_id_token', raise ApiError(
message="Cannot decode id_token", code="bad_id_token", message="Cannot decode id_token", status_code=401
status_code=401) )
try: try:
assert decoded_token['iss'] == f"{keycloak_server_url}/realms/{keycloak_realm_name}" assert (
assert keycloak_client_id in decoded_token['aud'] or 'account' in decoded_token['aud'] decoded_token["iss"]
if 'azp' in decoded_token: == f"{keycloak_server_url}/realms/{keycloak_realm_name}"
)
assert (
keycloak_client_id in decoded_token["aud"]
or "account" in decoded_token["aud"]
)
if "azp" in decoded_token:
# TODO: not sure why this isn't keycloak_client_id # TODO: not sure why this isn't keycloak_client_id
assert decoded_token['azp'] in keycloak_client_id, 'account' assert decoded_token["azp"] in keycloak_client_id, "account"
assert now > decoded_token['iat'] assert now > decoded_token["iat"]
except Exception as e: except Exception as e:
current_app.logger.error(f"Exception validating id_token: {e}") current_app.logger.error(f"Exception validating id_token: {e}")
return False return False
try: try:
assert now < decoded_token['exp'] assert now < decoded_token["exp"]
except: except:
raise ApiError(code='invalid_token', raise ApiError(
code="invalid_token",
message="Your token is expired. Please Login", message="Your token is expired. Please Login",
status_code=401) status_code=401,
)
return True return True
def get_public_access_token(self, username, password) -> dict: def get_public_access_token(self, username, password) -> dict:
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args() """Get_public_access_token."""
(
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = AuthorizationService.get_keycloak_args()
# Get public access token # Get public access token
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token"
headers = {"Content-Type": "application/x-www-form-urlencoded"} headers = {"Content-Type": "application/x-www-form-urlencoded"}
post_data = {'grant_type': 'password', post_data = {
'username': username, "grant_type": "password",
'password': password, "username": username,
'client_id': 'spiffworkflow-frontend' "password": password,
"client_id": "spiffworkflow-frontend",
} }
public_response = requests.post(request_url, headers=headers, data=post_data) public_response = requests.post(request_url, headers=headers, data=post_data)
if public_response.status_code == 200: if public_response.status_code == 200:
public_token = json.loads(public_response.text) public_token = json.loads(public_response.text)
if 'access_token' in public_token: if "access_token" in public_token:
return public_token['access_token'] return public_token["access_token"]
raise ApiError(code='no_public_access_token', raise ApiError(
message=f"We could not get a public access token: {username}") code="no_public_access_token",
message=f"We could not get a public access token: {username}",
)
class AuthenticationService: class AuthenticationService:
"""AuthenticationService.""" """AuthenticationService."""
class KeycloakAuthenticationService: class KeycloakAuthenticationService:
"""KeycloakAuthenticationService."""
@staticmethod @staticmethod
def get_keycloak_openid( def get_keycloak_openid(
@ -157,6 +210,7 @@ class KeycloakAuthenticationService:
client_secret_key=client_secret_key, client_secret_key=client_secret_key,
) )
return keycloak_openid return keycloak_openid
# #
# @staticmethod # @staticmethod
# def get_keycloak_token(keycloak_openid, user, password): # def get_keycloak_token(keycloak_openid, user, password):
@ -188,6 +242,7 @@ class KeycloakAuthenticationService:
# permissions: list = keycloak_openid.uma_permissions(token["access_token"]) # permissions: list = keycloak_openid.uma_permissions(token["access_token"])
# return permissions # return permissions
# #
@staticmethod @staticmethod
def get_uma_permissions_by_token_for_resource_and_scope( def get_uma_permissions_by_token_for_resource_and_scope(
keycloak_openid: KeycloakOpenID, token: dict, resource: str, scope: str keycloak_openid: KeycloakOpenID, token: dict, resource: str, scope: str
@ -197,6 +252,8 @@ class KeycloakAuthenticationService:
token["access_token"], permissions=f"{resource}#{scope}" token["access_token"], permissions=f"{resource}#{scope}"
) )
return permissions return permissions
# #
# @staticmethod # @staticmethod
# def get_auth_status_for_resource_and_scope_by_token( # def get_auth_status_for_resource_and_scope_by_token(
@ -228,5 +285,6 @@ class KeycloakAuthenticationService:
class KeyCloak: class KeyCloak:
"""KeyCloak."""
"""Class to interact with KeyCloak server for authorization""" """Class to interact with KeyCloak server for authorization."""

View File

@ -1,35 +1,46 @@
"""Authorization_service.""" """Authorization_service."""
import requests
import base64 import base64
import json import json
import jwt
import enum
from flask import g
from flask import current_app
from flask_bpmn.api.api_error import ApiError
from typing import Union from typing import Union
from spiffworkflow_backend.models.user import UserModel import jwt
import requests
from flask import current_app
from flask_bpmn.api.api_error import ApiError
class AuthorizationService: class AuthorizationService:
"""Determine whether a user has permission to perform their request.""" """Determine whether a user has permission to perform their request."""
@staticmethod @staticmethod
def get_keycloak_args(): def get_keycloak_args():
keycloak_server_url = current_app.config['KEYCLOAK_SERVER_URL'] """Get_keycloak_args."""
keycloak_server_url = current_app.config["KEYCLOAK_SERVER_URL"]
keycloak_client_id = current_app.config["KEYCLOAK_CLIENT_ID"] keycloak_client_id = current_app.config["KEYCLOAK_CLIENT_ID"]
keycloak_realm_name = current_app.config["KEYCLOAK_REALM_NAME"] keycloak_realm_name = current_app.config["KEYCLOAK_REALM_NAME"]
keycloak_client_secret_key = current_app.config["KEYCLOAK_CLIENT_SECRET_KEY"] # noqa: S105 keycloak_client_secret_key = current_app.config[
return keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key "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(self, token): def get_user_info_from_id_token(self, token):
"""This seems to work with basic tokens too""" """This seems to work with basic tokens too."""
json_data = None json_data = None
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args() (
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = AuthorizationService.get_keycloak_args()
BACKEND_BASIC_AUTH_STRING = f"{keycloak_client_id}:{keycloak_client_secret_key}" BACKEND_BASIC_AUTH_STRING = f"{keycloak_client_id}:{keycloak_client_secret_key}"
BACKEND_BASIC_AUTH_BYTES = bytes(BACKEND_BASIC_AUTH_STRING, encoding='ascii') BACKEND_BASIC_AUTH_BYTES = bytes(BACKEND_BASIC_AUTH_STRING, encoding="ascii")
BACKEND_BASIC_AUTH = base64.b64encode(BACKEND_BASIC_AUTH_BYTES) base64.b64encode(BACKEND_BASIC_AUTH_BYTES)
# headers = {"Content-Type": "application/x-www-form-urlencoded", # headers = {"Content-Type": "application/x-www-form-urlencoded",
# "Authorization": f"Bearer {BACKEND_BASIC_AUTH.decode('utf-8')}"} # "Authorization": f"Bearer {BACKEND_BASIC_AUTH.decode('utf-8')}"}
@ -46,11 +57,13 @@ class AuthorizationService:
# auth_bearer_string = f"Basic {keycloak_client_secret_key}" # auth_bearer_string = f"Basic {keycloak_client_secret_key}"
headers = {"Authorization": f"Bearer {token}"} headers = {"Authorization": f"Bearer {token}"}
data = {'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange', data = {
'client_id': keycloak_client_id, "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['access_token'],
"subject_token": token, "subject_token": token,
"audience": keycloak_client_id} "audience": keycloak_client_id,
}
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/userinfo" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/userinfo"
try: try:
@ -60,38 +73,56 @@ class AuthorizationService:
if request_response.status_code == 200: if request_response.status_code == 200:
json_data = json.loads(request_response.text) json_data = json.loads(request_response.text)
elif request_response.status_code == 401: elif request_response.status_code == 401:
raise ApiError(code="invalid_token", raise ApiError(
message="Please login", code="invalid_token", message="Please login", status_code=401
status_code=401) )
return json_data return json_data
def refresh_token(self, token): def refresh_token(self, token):
"""Refresh_token."""
# if isinstance(token, str): # if isinstance(token, str):
# token = eval(token) # token = eval(token)
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args() (
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = AuthorizationService.get_keycloak_args()
headers = {"Content-Type": "application/x-www-form-urlencoded"} headers = {"Content-Type": "application/x-www-form-urlencoded"}
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token"
data = {'grant_type': 'refresh_token', data = {
'client_id': 'spiffworkflow-frontend', "grant_type": "refresh_token",
'subject_token': token, "client_id": "spiffworkflow-frontend",
'refresh_token': token} "subject_token": token,
"refresh_token": token,
}
refresh_response = requests.post(request_url, headers=headers, data=data) refresh_response = requests.post(request_url, headers=headers, data=data)
refresh_token = json.loads(refresh_response.text) refresh_token = json.loads(refresh_response.text)
return refresh_token return refresh_token
def get_bearer_token(self, basic_token): def get_bearer_token(self, basic_token):
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args() """Get_bearer_token."""
(
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = AuthorizationService.get_keycloak_args()
BACKEND_BASIC_AUTH_STRING = f"{keycloak_client_id}:{keycloak_client_secret_key}" BACKEND_BASIC_AUTH_STRING = f"{keycloak_client_id}:{keycloak_client_secret_key}"
BACKEND_BASIC_AUTH_BYTES = bytes(BACKEND_BASIC_AUTH_STRING, encoding='ascii') BACKEND_BASIC_AUTH_BYTES = bytes(BACKEND_BASIC_AUTH_STRING, encoding="ascii")
BACKEND_BASIC_AUTH = base64.b64encode(BACKEND_BASIC_AUTH_BYTES) BACKEND_BASIC_AUTH = base64.b64encode(BACKEND_BASIC_AUTH_BYTES)
headers = {"Content-Type": "application/x-www-form-urlencoded", headers = {
"Authorization": f"Basic {BACKEND_BASIC_AUTH.decode('utf-8')}"} "Content-Type": "application/x-www-form-urlencoded",
data = {'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange', "Authorization": f"Basic {BACKEND_BASIC_AUTH.decode('utf-8')}",
'client_id': keycloak_client_id, }
data = {
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"client_id": keycloak_client_id,
"subject_token": basic_token, "subject_token": basic_token,
"audience": keycloak_client_id} "audience": keycloak_client_id,
}
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token"
backend_response = requests.post(request_url, headers=headers, data=data) backend_response = requests.post(request_url, headers=headers, data=data)
@ -126,20 +157,31 @@ class AuthorizationService:
) from exception ) from exception
def get_bearer_token_from_internal_token(self, internal_token): def get_bearer_token_from_internal_token(self, internal_token):
decoded_token = self.decode_auth_token(internal_token) """Get_bearer_token_from_internal_token."""
self.decode_auth_token(internal_token)
print(f"get_user_by_internal_token: {internal_token}") print(f"get_user_by_internal_token: {internal_token}")
def introspect_token(self, basic_token): def introspect_token(self, basic_token):
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args() """Introspect_token."""
(
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = AuthorizationService.get_keycloak_args()
bearer_token = AuthorizationService().get_bearer_token(basic_token) bearer_token = AuthorizationService().get_bearer_token(basic_token)
auth_bearer_string = f"Bearer {bearer_token['access_token']}" auth_bearer_string = f"Bearer {bearer_token['access_token']}"
headers = {"Content-Type": "application/x-www-form-urlencoded", headers = {
"Authorization": auth_bearer_string} "Content-Type": "application/x-www-form-urlencoded",
data = {'client_id': keycloak_client_id, "Authorization": auth_bearer_string,
'client_secret': keycloak_client_secret_key, }
'token': basic_token} data = {
"client_id": keycloak_client_id,
"client_secret": keycloak_client_secret_key,
"token": basic_token,
}
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token/introspect" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token/introspect"
introspect_response = requests.post(request_url, headers=headers, data=data) introspect_response = requests.post(request_url, headers=headers, data=data)
@ -148,7 +190,13 @@ class AuthorizationService:
return introspection return introspection
def get_permission_by_basic_token(self, basic_token): def get_permission_by_basic_token(self, basic_token):
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args() """Get_permission_by_basic_token."""
(
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = AuthorizationService.get_keycloak_args()
# basic_token = AuthorizationService().refresh_token(basic_token) # basic_token = AuthorizationService().refresh_token(basic_token)
# bearer_token = AuthorizationService().get_bearer_token(basic_token['access_token']) # bearer_token = AuthorizationService().get_bearer_token(basic_token['access_token'])
@ -156,36 +204,49 @@ class AuthorizationService:
# auth_bearer_string = f"Bearer {bearer_token['access_token']}" # auth_bearer_string = f"Bearer {bearer_token['access_token']}"
auth_bearer_string = f"Bearer {bearer_token}" auth_bearer_string = f"Bearer {bearer_token}"
headers = {"Content-Type": "application/x-www-form-urlencoded", headers = {
"Authorization": auth_bearer_string} "Content-Type": "application/x-www-form-urlencoded",
data = {'client_id': keycloak_client_id, "Authorization": auth_bearer_string,
'client_secret': keycloak_client_secret_key, }
"grant_type": 'urn:ietf:params:oauth:grant-type:uma-ticket', 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", "response_mode": "permissions",
"audience": keycloak_client_id, "audience": keycloak_client_id,
"response_include_resource_name": True "response_include_resource_name": True,
} }
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token"
permission_response = requests.post(request_url, headers=headers, data=data) permission_response = requests.post(request_url, headers=headers, data=data)
permission = json.loads(permission_response.text) permission = json.loads(permission_response.text)
return permission return permission
def get_auth_status_for_resource_and_scope_by_token(self, basic_token, resource, scope): def get_auth_status_for_resource_and_scope_by_token(
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args() self, basic_token, resource, scope
):
"""Get_auth_status_for_resource_and_scope_by_token."""
(
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = AuthorizationService.get_keycloak_args()
# basic_token = AuthorizationService().refresh_token(basic_token) # basic_token = AuthorizationService().refresh_token(basic_token)
bearer_token = AuthorizationService().get_bearer_token(basic_token) bearer_token = AuthorizationService().get_bearer_token(basic_token)
auth_bearer_string = f"Bearer {bearer_token['access_token']}" auth_bearer_string = f"Bearer {bearer_token['access_token']}"
headers = {"Content-Type": "application/x-www-form-urlencoded", headers = {
"Authorization": auth_bearer_string} "Content-Type": "application/x-www-form-urlencoded",
"Authorization": auth_bearer_string,
}
data = { data = {
'client_id': keycloak_client_id, "client_id": keycloak_client_id,
'client_secret': keycloak_client_secret_key, "client_secret": keycloak_client_secret_key,
"grant_type": 'urn:ietf:params:oauth:grant-type:uma-ticket', "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"permission": f"{resource}#{scope}", "permission": f"{resource}#{scope}",
"response_mode": "permissions", "response_mode": "permissions",
"audience": keycloak_client_id "audience": keycloak_client_id,
} }
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token"
auth_response = requests.post(request_url, headers=headers, data=data) auth_response = requests.post(request_url, headers=headers, data=data)
@ -194,28 +255,39 @@ class AuthorizationService:
auth_status = json.loads(auth_response.text) auth_status = json.loads(auth_response.text)
return auth_status return auth_status
def get_permissions_by_token_for_resource_and_scope(self, basic_token, resource=None, scope=None): def get_permissions_by_token_for_resource_and_scope(
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args() self, basic_token, resource=None, scope=None
):
"""Get_permissions_by_token_for_resource_and_scope."""
(
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = AuthorizationService.get_keycloak_args()
# basic_token = AuthorizationService().refresh_token(basic_token) # basic_token = AuthorizationService().refresh_token(basic_token)
# bearer_token = AuthorizationService().get_bearer_token(basic_token['access_token']) # bearer_token = AuthorizationService().get_bearer_token(basic_token['access_token'])
bearer_token = AuthorizationService().get_bearer_token(basic_token) bearer_token = AuthorizationService().get_bearer_token(basic_token)
auth_bearer_string = f"Bearer {bearer_token['access_token']}" auth_bearer_string = f"Bearer {bearer_token['access_token']}"
headers = {"Content-Type": "application/x-www-form-urlencoded", headers = {
"Authorization": auth_bearer_string} "Content-Type": "application/x-www-form-urlencoded",
permision = '' "Authorization": auth_bearer_string,
}
permision = ""
if resource: if resource:
permision += resource permision += resource
if scope: if scope:
permision += '#' + resource permision += "#" + resource
data = {'client_id': keycloak_client_id, data = {
'client_secret': keycloak_client_secret_key, "client_id": keycloak_client_id,
"grant_type": 'urn:ietf:params:oauth:grant-type:uma-ticket', "client_secret": keycloak_client_secret_key,
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"response_mode": "permissions", "response_mode": "permissions",
"permission": permision, "permission": permision,
"audience": keycloak_client_id, "audience": keycloak_client_id,
"response_include_resource_name": True "response_include_resource_name": True,
} }
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token"
permission_response = requests.post(request_url, headers=headers, data=data) permission_response = requests.post(request_url, headers=headers, data=data)
@ -223,16 +295,26 @@ class AuthorizationService:
return permission return permission
def get_resource_set(self, public_access_token, uri): def get_resource_set(self, public_access_token, uri):
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args() """Get_resource_set."""
(
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = AuthorizationService.get_keycloak_args()
bearer_token = AuthorizationService().get_bearer_token(public_access_token) bearer_token = AuthorizationService().get_bearer_token(public_access_token)
auth_bearer_string = f"Bearer {bearer_token['access_token']}" auth_bearer_string = f"Bearer {bearer_token['access_token']}"
headers = {"Content-Type": "application/json", headers = {
"Authorization": auth_bearer_string} "Content-Type": "application/json",
data = {'matchingUri': 'true', "Authorization": auth_bearer_string,
'deep': 'true', }
'max': '-1', data = {
'exactName': 'false', "matchingUri": "true",
'uri': uri} "deep": "true",
"max": "-1",
"exactName": "false",
"uri": uri,
}
# f"matchingUri=true&deep=true&max=-1&exactName=false&uri={URI_TO_TEST_AGAINST}" # f"matchingUri=true&deep=true&max=-1&exactName=false&uri={URI_TO_TEST_AGAINST}"
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/authz/protection/resource_set" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/authz/protection/resource_set"
@ -241,13 +323,23 @@ class AuthorizationService:
print("get_resource_set") print("get_resource_set")
def get_permission_by_token(self, public_access_token) -> dict: def get_permission_by_token(self, public_access_token) -> dict:
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = AuthorizationService.get_keycloak_args() """Get_permission_by_token."""
(
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = AuthorizationService.get_keycloak_args()
bearer_token = AuthorizationService().get_bearer_token(public_access_token) bearer_token = AuthorizationService().get_bearer_token(public_access_token)
auth_bearer_string = f"Bearer {bearer_token['access_token']}" auth_bearer_string = f"Bearer {bearer_token['access_token']}"
headers = {"Content-Type": "application/x-www-form-urlencoded", headers = {
"Authorization": auth_bearer_string} "Content-Type": "application/x-www-form-urlencoded",
data = {"grant_type": 'urn:ietf:params:oauth:grant-type:uma-ticket', "Authorization": auth_bearer_string,
"audience": keycloak_client_id} }
data = {
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
"audience": keycloak_client_id,
}
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token"
permission_response = requests.post(request_url, headers=headers, data=data) permission_response = requests.post(request_url, headers=headers, data=data)
permission = json.loads(permission_response.text) permission = json.loads(permission_response.text)

View File

@ -15,15 +15,18 @@ class UserService:
"""Provides common tools for working with users.""" """Provides common tools for working with users."""
def create_user(self, service, service_id, name=None, username=None, email=None): def create_user(self, service, service_id, name=None, username=None, email=None):
user = UserModel.query.filter(UserModel.service == service)\ """Create_user."""
.filter(UserModel.service_id == service_id)\ user = (
UserModel.query.filter(UserModel.service == service)
.filter(UserModel.service_id == service_id)
.first() .first()
)
if name is None: if name is None:
name = '' name = ""
if username is None: if username is None:
username = '' username = ""
if email is None: if email is None:
email = '' email = ""
if user is not None: if user is not None:
raise ( raise (
@ -34,24 +37,29 @@ class UserService:
) )
) )
user = UserModel(username=username, user = UserModel(
username=username,
service=service, service=service,
service_id=service_id, service_id=service_id,
name=name, name=name,
email=email) email=email,
)
try: try:
db.session.add(user) db.session.add(user)
except IntegrityError as exception: except IntegrityError as exception:
raise ( raise (
ApiError(code="integrity_error", message=repr(exception), status_code=500) ApiError(
code="integrity_error", message=repr(exception), status_code=500
)
) from exception ) from exception
try: try:
db.session.commit() db.session.commit()
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
raise ApiError(code='add_user_error', raise ApiError(
message=f'Could not add user {username}') from e code="add_user_error", message=f"Could not add user {username}"
) from e
try: try:
self.create_principal(user.id) self.create_principal(user.id)
except ApiError as ae: except ApiError as ae:
@ -234,6 +242,7 @@ class UserService:
) )
def create_principal(self, user_id): def create_principal(self, user_id):
"""Create_principal."""
principal = PrincipalModel.query.filter_by(user_id=user_id).first() principal = PrincipalModel.query.filter_by(user_id=user_id).first()
if principal is None: if principal is None:
principal = PrincipalModel(user_id=user_id) principal = PrincipalModel(user_id=user_id)
@ -243,6 +252,8 @@ class UserService:
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
current_app.logger.error(f"Exception in create_principal: {e}") current_app.logger.error(f"Exception in create_principal: {e}")
raise ApiError(code="add_principal_error", raise ApiError(
message=f"Could not create principal {user_id}") from e code="add_principal_error",
message=f"Could not create principal {user_id}",
) from e
return principal return principal

View File

@ -1,6 +1,9 @@
"""Base_test.""" """Base_test."""
from flask.app import Flask from flask.app import Flask
from spiffworkflow_backend.services.authentication_service import PublicAuthenticationService
from spiffworkflow_backend.services.authentication_service import (
PublicAuthenticationService,
)
class BaseTest: class BaseTest:
@ -9,15 +12,24 @@ class BaseTest:
@staticmethod @staticmethod
def get_keycloak_constants(app: Flask) -> tuple: def get_keycloak_constants(app: Flask) -> tuple:
"""Get_keycloak_constants.""" """Get_keycloak_constants."""
keycloak_server_url = app.config['KEYCLOAK_SERVER_URL'] keycloak_server_url = app.config["KEYCLOAK_SERVER_URL"]
keycloak_client_id = app.config["KEYCLOAK_CLIENT_ID"] keycloak_client_id = app.config["KEYCLOAK_CLIENT_ID"]
keycloak_realm_name = app.config["KEYCLOAK_REALM_NAME"] keycloak_realm_name = app.config["KEYCLOAK_REALM_NAME"]
keycloak_client_secret_key = app.config["KEYCLOAK_CLIENT_SECRET_KEY"] # noqa: S105 keycloak_client_secret_key = app.config[
"KEYCLOAK_CLIENT_SECRET_KEY"
] # noqa: S105
return keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key return (
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
)
@staticmethod @staticmethod
def get_public_access_token(username, password) -> dict: def get_public_access_token(username, password) -> dict:
public_access_token = PublicAuthenticationService().get_public_access_token(username, password) """Get_public_access_token."""
public_access_token = PublicAuthenticationService().get_public_access_token(
username, password
)
return public_access_token return public_access_token

View File

@ -1,25 +1,17 @@
"""Test_authentication.""" """Test_authentication."""
import json
import requests
import base64 import base64
import urllib.parse import json
from typing import Any
import requests
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient
from keycloak.authorization import Authorization # type: ignore from keycloak.authorization import Authorization # type: ignore
from keycloak.keycloak_openid import KeycloakOpenID # type: ignore from keycloak.keycloak_openid import KeycloakOpenID # type: ignore
from keycloak.uma_permissions import AuthStatus # type: ignore from keycloak.uma_permissions import AuthStatus # type: ignore
from tests.spiffworkflow_backend.integration.base_test import BaseTest from tests.spiffworkflow_backend.integration.base_test import BaseTest
from spiffworkflow_backend.services.authentication_service import PublicAuthenticationService, KeycloakAuthenticationService
from spiffworkflow_backend.services.authorization_service import AuthorizationService
from urllib.parse import urlencode
class TestAuthentication(BaseTest): class TestAuthentication(BaseTest):
"""TestAuthentication."""
# def test_get_basic_token(self, app: Flask) -> None: # def test_get_basic_token(self, app: Flask) -> None:
# for user_id in ('user_1', 'user_2', 'admin_1', 'admin_2'): # for user_id in ('user_1', 'user_2', 'admin_1', 'admin_2'):
@ -35,34 +27,45 @@ class TestAuthentication(BaseTest):
# assert isinstance(basic_token['scope'], str) # assert isinstance(basic_token['scope'], str)
def test_get_token_script(self, app: Flask) -> None: def test_get_token_script(self, app: Flask) -> None:
"""Test_get_token_script."""
print("Test Get Token Script") print("Test Get Token Script")
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = self.get_keycloak_constants(app) (
keycloak_user = 'ciuser1' keycloak_server_url,
keycloak_pass = 'ciuser1' keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = self.get_keycloak_constants(app)
keycloak_user = "ciuser1"
keycloak_pass = "ciuser1"
print(f"Test Get Token Script: keycloak_server_url: {keycloak_server_url}") print(f"Test Get Token Script: keycloak_server_url: {keycloak_server_url}")
print(f"Test Get Token Script: keycloak_client_id: {keycloak_client_id}") print(f"Test Get Token Script: keycloak_client_id: {keycloak_client_id}")
print(f"Test Get Token Script: keycloak_realm_name: {keycloak_realm_name}") print(f"Test Get Token Script: keycloak_realm_name: {keycloak_realm_name}")
print(f"Test Get Token Script: keycloak_client_secret_key: {keycloak_client_secret_key}") print(
f"Test Get Token Script: keycloak_client_secret_key: {keycloak_client_secret_key}"
)
frontend_client_id = 'spiffworkflow-frontend' frontend_client_id = "spiffworkflow-frontend"
print(f"Test Get Token Script: frontend_client_id: {frontend_client_id}") print(f"Test Get Token Script: frontend_client_id: {frontend_client_id}")
# Get frontend token # Get frontend token
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token"
headers = {"Content-Type": "application/x-www-form-urlencoded"} headers = {"Content-Type": "application/x-www-form-urlencoded"}
post_data = {'grant_type': 'password', post_data = {
'username': keycloak_user, "grant_type": "password",
'password': keycloak_pass, "username": keycloak_user,
'client_id': frontend_client_id "password": keycloak_pass,
"client_id": frontend_client_id,
} }
print(f"Test Get Token Script: request_url: {request_url}") print(f"Test Get Token Script: request_url: {request_url}")
print(f"Test Get Token Script: headers: {headers}") print(f"Test Get Token Script: headers: {headers}")
print(f"Test Get Token Script: post_data: {post_data}") print(f"Test Get Token Script: post_data: {post_data}")
frontend_response = requests.post(request_url, headers=headers, json=post_data, data=post_data) frontend_response = requests.post(
request_url, headers=headers, json=post_data, data=post_data
)
frontend_token = json.loads(frontend_response.text) frontend_token = json.loads(frontend_response.text)
print(f"Test Get Token Script: frontend_response: {frontend_response}") print(f"Test Get Token Script: frontend_response: {frontend_response}")
@ -77,37 +80,44 @@ class TestAuthentication(BaseTest):
# Get backend token # Get backend token
BACKEND_BASIC_AUTH_STRING = f"{keycloak_client_id}:{keycloak_client_secret_key}" BACKEND_BASIC_AUTH_STRING = f"{keycloak_client_id}:{keycloak_client_secret_key}"
BACKEND_BASIC_AUTH_BYTES = bytes(BACKEND_BASIC_AUTH_STRING, encoding='ascii') BACKEND_BASIC_AUTH_BYTES = bytes(BACKEND_BASIC_AUTH_STRING, encoding="ascii")
BACKEND_BASIC_AUTH = base64.b64encode(BACKEND_BASIC_AUTH_BYTES) BACKEND_BASIC_AUTH = base64.b64encode(BACKEND_BASIC_AUTH_BYTES)
request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token" request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/token"
headers = {"Content-Type": "application/x-www-form-urlencoded", headers = {
"Authorization": f"Basic {BACKEND_BASIC_AUTH.decode('utf-8')}"} "Content-Type": "application/x-www-form-urlencoded",
data = {'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange', "Authorization": f"Basic {BACKEND_BASIC_AUTH.decode('utf-8')}",
'client_id': keycloak_client_id, }
"subject_token": frontend_token['access_token'], data = {
"audience": keycloak_client_id} "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"client_id": keycloak_client_id,
"subject_token": frontend_token["access_token"],
"audience": keycloak_client_id,
}
print(f"Test Get Token Script: request_url: {request_url}") print(f"Test Get Token Script: request_url: {request_url}")
print(f"Test Get Token Script: headers: {headers}") print(f"Test Get Token Script: headers: {headers}")
print(f"Test Get Token Script: data: {data}") print(f"Test Get Token Script: data: {data}")
backend_response = requests.post(request_url, headers=headers, data=data) backend_response = requests.post(request_url, headers=headers, data=data)
json_data = json.loads(backend_response.text) json_data = json.loads(backend_response.text)
backend_token = json_data['access_token'] backend_token = json_data["access_token"]
print(f"Test Get Token Script: backend_response: {backend_response}") print(f"Test Get Token Script: backend_response: {backend_response}")
print(f"Test Get Token Script: backend_token: {backend_token}") print(f"Test Get Token Script: backend_token: {backend_token}")
if backend_token: if backend_token:
# Getting resource set # Getting resource set
auth_bearer_string = f"Bearer {backend_token}" auth_bearer_string = f"Bearer {backend_token}"
headers = {"Content-Type": "application/json", headers = {
"Authorization": auth_bearer_string} "Content-Type": "application/json",
"Authorization": auth_bearer_string,
}
# URI_TO_TEST_AGAINST = "%2Fprocess-models" # URI_TO_TEST_AGAINST = "%2Fprocess-models"
URI_TO_TEST_AGAINST = "/status" URI_TO_TEST_AGAINST = "/status"
request_url = \ request_url = (
f"{keycloak_server_url}/realms/{keycloak_realm_name}/authz/protection/resource_set?" + \ f"{keycloak_server_url}/realms/{keycloak_realm_name}/authz/protection/resource_set?"
f"matchingUri=true&deep=true&max=-1&exactName=false&uri={URI_TO_TEST_AGAINST}" + f"matchingUri=true&deep=true&max=-1&exactName=false&uri={URI_TO_TEST_AGAINST}"
)
# f"uri={URI_TO_TEST_AGAINST}" # f"uri={URI_TO_TEST_AGAINST}"
print(f"Test Get Token Script: request_url: {request_url}") print(f"Test Get Token Script: request_url: {request_url}")
print(f"Test Get Token Script: headers: {headers}") print(f"Test Get Token Script: headers: {headers}")
@ -118,35 +128,43 @@ class TestAuthentication(BaseTest):
json_data = json.loads(resource_result.text) json_data = json.loads(resource_result.text)
resource_id_name_pairs = [] resource_id_name_pairs = []
for result in json_data: for result in json_data:
if '_id' in result and result['_id']: if "_id" in result and result["_id"]:
pair_key = result['_id'] pair_key = result["_id"]
if 'name' in result and result['name']: if "name" in result and result["name"]:
pair_value = result['name'] pair_value = result["name"]
# pair = {{result['_id']}: {}} # pair = {{result['_id']}: {}}
else: else:
pair_value = 'no_name' pair_value = "no_name"
# pair = {{result['_id']}: } # pair = {{result['_id']}: }
pair = [pair_key, pair_value] pair = [pair_key, pair_value]
resource_id_name_pairs.append(pair) resource_id_name_pairs.append(pair)
print(f"Test Get Token Script: resource_id_name_pairs: {resource_id_name_pairs}") print(
f"Test Get Token Script: resource_id_name_pairs: {resource_id_name_pairs}"
)
# Getting Permissions # Getting Permissions
for resource_id_name_pair in resource_id_name_pairs: for resource_id_name_pair in resource_id_name_pairs:
resource_id = resource_id_name_pair[0] resource_id = resource_id_name_pair[0]
resource_name = resource_id_name_pair[1] resource_id_name_pair[1]
headers = {"Content-Type": "application/x-www-form-urlencoded", headers = {
"Authorization": f"Basic {BACKEND_BASIC_AUTH.decode('utf-8')}"} "Content-Type": "application/x-www-form-urlencoded",
"Authorization": f"Basic {BACKEND_BASIC_AUTH.decode('utf-8')}",
}
post_data = {"audience": keycloak_client_id, post_data = {
"audience": keycloak_client_id,
"permission": resource_id, "permission": resource_id,
"subject_token": backend_token, "subject_token": backend_token,
"grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket"} "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
}
print(f"Test Get Token Script: headers: {headers}") print(f"Test Get Token Script: headers: {headers}")
print(f"Test Get Token Script: post_data: {post_data}") print(f"Test Get Token Script: post_data: {post_data}")
print(f"Test Get Token Script: request_url: {request_url}") print(f"Test Get Token Script: request_url: {request_url}")
permission_result = requests.post(request_url, headers=headers, data=post_data) permission_result = requests.post(
request_url, headers=headers, data=post_data
)
print(f"Test Get Token Script: permission_result: {permission_result}") print(f"Test Get Token Script: permission_result: {permission_result}")
print("test_get_token_script") print("test_get_token_script")
@ -155,6 +173,7 @@ class TestAuthentication(BaseTest):
# keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = self.get_keycloak_constants(app) # keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = self.get_keycloak_constants(app)
# request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/auth" # request_url = f"{keycloak_server_url}/realms/{keycloak_realm_name}/protocol/openid-connect/auth"
# class TestOtherStuff(BaseTest): # class TestOtherStuff(BaseTest):
# #
# # def test_get_backend_token(self, app: Flask) -> None: # # def test_get_backend_token(self, app: Flask) -> None:

View File

@ -1,8 +1,6 @@
"""Test_authorization.""" """Test_authorization."""
import requests # type: ignore import requests # type: ignore
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.integration.base_test import BaseTest from tests.spiffworkflow_backend.integration.base_test import BaseTest
from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import AuthorizationService
@ -12,82 +10,95 @@ from spiffworkflow_backend.services.authorization_service import AuthorizationSe
class TestAuthorization(BaseTest): class TestAuthorization(BaseTest):
"""TestAuthorization.""" """TestAuthorization."""
def test_get_bearer_token(self, app: Flask) -> None: def test_get_bearer_token(self, app: Flask) -> None:
for user_id in ('user_1', 'user_2', 'admin_1', 'admin_2'): """Test_get_bearer_token."""
for user_id in ("user_1", "user_2", "admin_1", "admin_2"):
public_access_token = self.get_public_access_token(user_id, user_id) public_access_token = self.get_public_access_token(user_id, user_id)
bearer_token = AuthorizationService().get_bearer_token(public_access_token) bearer_token = AuthorizationService().get_bearer_token(public_access_token)
assert isinstance(public_access_token, str) assert isinstance(public_access_token, str)
assert isinstance(bearer_token, dict) assert isinstance(bearer_token, dict)
assert 'access_token' in bearer_token assert "access_token" in bearer_token
assert isinstance(bearer_token['access_token'], str) assert isinstance(bearer_token["access_token"], str)
assert 'refresh_token' in bearer_token assert "refresh_token" in bearer_token
assert isinstance(bearer_token['refresh_token'], str) assert isinstance(bearer_token["refresh_token"], str)
assert 'token_type' in bearer_token assert "token_type" in bearer_token
assert bearer_token['token_type'] == 'Bearer' assert bearer_token["token_type"] == "Bearer"
assert 'scope' in bearer_token assert "scope" in bearer_token
assert isinstance(bearer_token['scope'], str) assert isinstance(bearer_token["scope"], str)
def test_get_user_info_from_public_access_token(self, app: Flask) -> None: def test_get_user_info_from_public_access_token(self, app: Flask) -> None:
for user_id in ('user_1', 'user_2', 'admin_1', 'admin_2'): """Test_get_user_info_from_public_access_token."""
for user_id in ("user_1", "user_2", "admin_1", "admin_2"):
public_access_token = self.get_public_access_token(user_id, user_id) public_access_token = self.get_public_access_token(user_id, user_id)
user_info = AuthorizationService().get_user_info_from_public_access_token(public_access_token) user_info = AuthorizationService().get_user_info_from_public_access_token(
assert 'sub' in user_info public_access_token
assert isinstance(user_info['sub'], str) )
assert len(user_info['sub']) == 36 assert "sub" in user_info
assert 'preferred_username' in user_info assert isinstance(user_info["sub"], str)
assert user_info['preferred_username'] == user_id assert len(user_info["sub"]) == 36
assert 'email' in user_info assert "preferred_username" in user_info
assert user_info['email'] == f"{user_id}@example.com" assert user_info["preferred_username"] == user_id
assert "email" in user_info
assert user_info["email"] == f"{user_id}@example.com"
def test_introspect_token(self, app: Flask) -> None: def test_introspect_token(self, app: Flask) -> None:
keycloak_server_url, keycloak_client_id, keycloak_realm_name, keycloak_client_secret_key = self.get_keycloak_constants(app) """Test_introspect_token."""
for user_id in ('user_1', 'user_2', 'admin_1', 'admin_2'): (
keycloak_server_url,
keycloak_client_id,
keycloak_realm_name,
keycloak_client_secret_key,
) = self.get_keycloak_constants(app)
for user_id in ("user_1", "user_2", "admin_1", "admin_2"):
basic_token = self.get_public_access_token(user_id, user_id) basic_token = self.get_public_access_token(user_id, user_id)
introspection = AuthorizationService().introspect_token(basic_token) introspection = AuthorizationService().introspect_token(basic_token)
assert isinstance(introspection, dict) assert isinstance(introspection, dict)
assert introspection['typ'] == 'Bearer' assert introspection["typ"] == "Bearer"
assert introspection['preferred_username'] == user_id assert introspection["preferred_username"] == user_id
assert introspection['client_id'] == 'spiffworkflow-frontend' assert introspection["client_id"] == "spiffworkflow-frontend"
assert 'resource_access' in introspection assert "resource_access" in introspection
resource_access = introspection['resource_access'] resource_access = introspection["resource_access"]
assert isinstance(resource_access, dict) assert isinstance(resource_access, dict)
assert keycloak_client_id in resource_access assert keycloak_client_id in resource_access
client = resource_access[keycloak_client_id] client = resource_access[keycloak_client_id]
assert 'roles' in client assert "roles" in client
roles = client['roles'] roles = client["roles"]
assert isinstance(roles, list) assert isinstance(roles, list)
if user_id == 'admin_1': if user_id == "admin_1":
assert len(roles) == 2 assert len(roles) == 2
for role in roles: for role in roles:
assert role in ('User', 'Admin') assert role in ("User", "Admin")
elif user_id == 'admin_2': elif user_id == "admin_2":
assert len(roles) == 1 assert len(roles) == 1
assert roles[0] == 'User' assert roles[0] == "User"
elif user_id == 'user_1' or user_id == 'user_2': elif user_id == "user_1" or user_id == "user_2":
assert len(roles) == 2 assert len(roles) == 2
for role in roles: for role in roles:
assert role in ('User', 'Anonymous') assert role in ("User", "Anonymous")
def test_get_permission_by_token(self, app: Flask) -> None: def test_get_permission_by_token(self, app: Flask) -> None:
resource_names = 'Default Resource', 'Process Groups', 'Process Models' """Test_get_permission_by_token."""
output = {} output = {}
for user_id in ('user_1', 'user_2', 'admin_1', 'admin_2'): for user_id in ("user_1", "user_2", "admin_1", "admin_2"):
output[user_id] = {} output[user_id] = {}
basic_token = self.get_public_access_token(user_id, user_id) basic_token = self.get_public_access_token(user_id, user_id)
permissions = AuthorizationService().get_permission_by_basic_token(basic_token) permissions = AuthorizationService().get_permission_by_basic_token(
basic_token
)
if isinstance(permissions, list): if isinstance(permissions, list):
for permission in permissions: for permission in permissions:
resource_name = permission['rsname'] resource_name = permission["rsname"]
output[user_id][resource_name] = {} output[user_id][resource_name] = {}
# assert resource_name in resource_names # assert resource_name in resource_names
# if resource_name == 'Process Groups' or resource_name == 'Process Models': # if resource_name == 'Process Groups' or resource_name == 'Process Models':
if 'scopes' in permission: if "scopes" in permission:
# assert 'scopes' in permission # assert 'scopes' in permission
scopes = permission['scopes'] scopes = permission["scopes"]
output[user_id][resource_name]['scopes'] = scopes output[user_id][resource_name]["scopes"] = scopes
# assert isinstance(scopes, list) # assert isinstance(scopes, list)
# assert len(scopes) == 1 # assert len(scopes) == 1
# assert scopes[0] == 'read' # assert scopes[0] == 'read'
@ -128,15 +139,16 @@ class TestAuthorization(BaseTest):
print("test_get_permission_by_token") print("test_get_permission_by_token")
def test_get_auth_status_for_resource_and_scope_by_token(self, app: Flask) -> None: def test_get_auth_status_for_resource_and_scope_by_token(self, app: Flask) -> None:
resources = 'Admin', 'Process Groups', 'Process Models' """Test_get_auth_status_for_resource_and_scope_by_token."""
resources = "Admin", "Process Groups", "Process Models"
# scope = 'read' # scope = 'read'
output = {} output = {}
for user_id in ('user_1', 'user_2', 'admin_1', 'admin_2'): for user_id in ("user_1", "user_2", "admin_1", "admin_2"):
output[user_id] = {} output[user_id] = {}
basic_token = self.get_public_access_token(user_id, user_id) basic_token = self.get_public_access_token(user_id, user_id)
for resource in resources: for resource in resources:
output[user_id][resource] = {} output[user_id][resource] = {}
for scope in 'instantiate', 'read', 'update', 'delete': for scope in "instantiate", "read", "update", "delete":
auth_status = AuthorizationService().get_auth_status_for_resource_and_scope_by_token( auth_status = AuthorizationService().get_auth_status_for_resource_and_scope_by_token(
basic_token, resource, scope basic_token, resource, scope
) )
@ -144,20 +156,21 @@ class TestAuthorization(BaseTest):
print("test_get_auth_status_for_resource_and_scope_by_token") print("test_get_auth_status_for_resource_and_scope_by_token")
def test_get_permissions_by_token_for_resource_and_scope(self, app: Flask): def test_get_permissions_by_token_for_resource_and_scope(self, app: Flask):
resource_names = 'Default Resource', 'Process Groups', 'Process Models' """Test_get_permissions_by_token_for_resource_and_scope."""
resource_names = "Default Resource", "Process Groups", "Process Models"
output = {} output = {}
for user_id in ('user_1', 'user_2', 'admin_1', 'admin_2'): for user_id in ("user_1", "user_2", "admin_1", "admin_2"):
output[user_id] = {} output[user_id] = {}
basic_token = self.get_public_access_token(user_id, user_id) basic_token = self.get_public_access_token(user_id, user_id)
for resource in resource_names: for resource in resource_names:
output[user_id][resource] = {} output[user_id][resource] = {}
for scope in 'instantiate', 'read', 'update', 'delete': for scope in "instantiate", "read", "update", "delete":
permissions = AuthorizationService().\ permissions = AuthorizationService().get_permissions_by_token_for_resource_and_scope(
get_permissions_by_token_for_resource_and_scope(basic_token, resource, scope) basic_token, resource, scope
)
output[user_id][resource][scope] = permissions output[user_id][resource][scope] = permissions
print("test_get_permissions_by_token_for_resource_and_scope") print("test_get_permissions_by_token_for_resource_and_scope")
# # def test_authorize_action(self, app: Flask, client: FlaskClient) -> None: # # def test_authorize_action(self, app: Flask, client: FlaskClient) -> None:
# # action = 'my_action' # # action = 'my_action'
# # result = app.get(f'/authorize/{action}') # # result = app.get(f'/authorize/{action}')