From 2630dbfb459ed0478b0f51fcc3c6fd690b7f0acc Mon Sep 17 00:00:00 2001 From: jasquat Date: Wed, 11 Jan 2023 17:27:12 -0500 Subject: [PATCH] use the cookie from the frontend w/ burnettk --- .../src/spiffworkflow_backend/__init__.py | 3 +- .../spiffworkflow_backend/config/default.py | 4 +- .../openid_blueprint/openid_blueprint.py | 2 - .../src/spiffworkflow_backend/routes/user.py | 52 ++++++++++++------- .../services/authentication_service.py | 30 +++++------ spiffworkflow-frontend/package-lock.json | 14 +++++ spiffworkflow-frontend/package.json | 2 + spiffworkflow-frontend/src/index.tsx | 2 +- .../src/routes/AuthenticationList.tsx | 2 +- .../src/routes/ProcessGroupList.tsx | 1 - .../src/services/HttpService.ts | 2 +- .../src/services/UserService.ts | 47 +++++++---------- 12 files changed, 91 insertions(+), 70 deletions(-) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py index bda2f03c8..4ffc2f59f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/__init__.py @@ -23,7 +23,7 @@ from spiffworkflow_backend.routes.admin_blueprint.admin_blueprint import admin_b from spiffworkflow_backend.routes.openid_blueprint.openid_blueprint import ( openid_blueprint, ) -from spiffworkflow_backend.routes.user import verify_token +from spiffworkflow_backend.routes.user import set_new_access_token_in_cookie, verify_token from spiffworkflow_backend.routes.user_blueprint import user_blueprint from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.background_processing_service import ( @@ -131,6 +131,7 @@ def create_app() -> flask.app.Flask: app.before_request(verify_token) app.before_request(AuthorizationService.check_for_permission) + app.after_request(set_new_access_token_in_cookie) return app # type: ignore diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py index 6e20357e8..0032a8a80 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py @@ -29,8 +29,8 @@ CONNECTOR_PROXY_URL = environ.get( # Open ID server OPEN_ID_SERVER_URL = environ.get( - # "OPEN_ID_SERVER_URL", default="http://localhost:7002/realms/spiffworkflow" - "OPEN_ID_SERVER_URL", default="http://localhost:7000/openid" + "OPEN_ID_SERVER_URL", default="http://localhost:7002/realms/spiffworkflow" + # "OPEN_ID_SERVER_URL", default="http://localhost:7000/openid" ) # Replace above line with this to use the built-in Open ID Server. diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py index fa913528f..f25100eed 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/openid_blueprint/openid_blueprint.py @@ -34,8 +34,6 @@ def well_known() -> dict: These urls can be very different from one openid impl to the next, this is just a small subset. """ host_url = request.host_url.strip("/") - print(f"host_url: {host_url}") - print(f"request.path: {request.url}") return { "issuer": f"{host_url}/openid", "authorization_endpoint": f"{host_url}{url_for('openid.auth')}", diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py index ffe523da5..c1966c3ae 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py @@ -14,9 +14,11 @@ from flask import redirect from flask import request from flask_bpmn.api.api_error import ApiError from werkzeug.wrappers import Response +# from flask.wrappers import Response +import flask from spiffworkflow_backend.models.user import UserModel -from spiffworkflow_backend.services.authentication_service import AuthenticationService +from spiffworkflow_backend.services.authentication_service import TokenExpiredError, AuthenticationService from spiffworkflow_backend.services.authentication_service import ( MissingAccessTokenError, ) @@ -57,6 +59,11 @@ def verify_token( if not token and "Authorization" in request.headers: token = request.headers["Authorization"].removeprefix("Bearer ") + # This should never be set here but just in case + tld = current_app.config['THREAD_LOCAL_DATA'] + if hasattr(tld, "new_access_token"): + tld.new_access_token = None + if token: user_model = None decoded_token = get_decoded_token(token) @@ -73,13 +80,11 @@ def verify_token( f" internal token. {e}" ) elif "iss" in decoded_token.keys(): + user_info = None try: - if AuthenticationService.validate_id_token(token): + if AuthenticationService.validate_id_or_access_token(token): user_info = decoded_token - except ( - ApiError - ) as ae: # API Error is only thrown in the token is outdated. - print("HEY WE IN ERROR") + except (TokenExpiredError) as token_expired_error: # Try to refresh the token user = UserService.get_user_by_service_and_service_id( decoded_token["iss"], decoded_token["sub"] @@ -92,26 +97,28 @@ def verify_token( refresh_token ) ) - # set_access_cookies() - print(f"auth_token: {auth_token}") if auth_token and "error" not in auth_token: + print("SETTING NEW TOKEN") + print(f"auth_token: {auth_token}") + tld.new_access_token = auth_token['access_token'] # We have the user, but this code is a bit convoluted, and will later demand # a user_info object so it can look up the user. Sorry to leave this crap here. user_info = {"sub": user.service_id, "iss": user.service} - else: - raise ae - else: - raise ae - else: - raise ae + + if user_info is None: + raise ApiError( + error_code="invalid_token", + message="Your token is expired. Please Login", + status_code=401, + ) from token_expired_error + except Exception as e: - current_app.logger.error(f"Exception raised in get_token: {e}") raise ApiError( error_code="fail_get_user_info", message="Cannot get user info from token", status_code=401, ) from e - print(f"USER_INFO: {user_info}") + if ( user_info is not None and "error" not in user_info @@ -165,6 +172,14 @@ def verify_token( ) +def set_new_access_token_in_cookie(response: flask.wrappers.Response) -> flask.wrappers.Response: + print(f"response: {response.__class__}") + tld = current_app.config['THREAD_LOCAL_DATA'] + if hasattr(tld, "new_access_token") and tld.new_access_token: + response.set_cookie('access_token', tld.new_access_token) + return response + + def validate_scope(token: Any) -> bool: """Validate_scope.""" print("validate_scope") @@ -220,7 +235,6 @@ def parse_id_token(token: str) -> Any: decoded = base64.b64decode(padded) return json.loads(decoded) - def login_return(code: str, state: str, session_state: str) -> Optional[Response]: """Login_return.""" state_dict = ast.literal_eval(base64.b64decode(state).decode("utf-8")) @@ -231,7 +245,7 @@ def login_return(code: str, state: str, session_state: str) -> Optional[Response user_info = parse_id_token(id_token) - if AuthenticationService.validate_id_token(id_token): + if AuthenticationService.validate_id_or_access_token(id_token): if user_info and "error" not in user_info: user_model = AuthorizationService.create_user_from_sign_in(user_info) g.user = user_model.id @@ -244,6 +258,8 @@ def login_return(code: str, state: str, session_state: str) -> Optional[Response + f"access_token={auth_token_object['access_token']}&" + f"id_token={id_token}" ) + tld = current_app.config['THREAD_LOCAL_DATA'] + tld.new_access_token = auth_token_object['access_token'] return redirect(redirect_url) raise ApiError( diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py index cf1ed8fcb..309dca1e1 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/authentication_service.py @@ -20,6 +20,15 @@ class MissingAccessTokenError(Exception): """MissingAccessTokenError.""" +# These could be either 'id' OR 'access' tokens and we can't always know which +class TokenExpiredError(Exception): + pass + + +class TokenInvalidError(Exception): + pass + + class AuthenticationProviderTypes(enum.Enum): """AuthenticationServiceProviders.""" @@ -128,21 +137,15 @@ class AuthenticationService: return auth_token_object @classmethod - def validate_id_token(cls, id_token: str) -> bool: + def validate_id_or_access_token(cls, token: str) -> bool: """Https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation.""" valid = True now = time.time() try: - decoded_token = jwt.decode(id_token, options={"verify_signature": False}) + decoded_token = jwt.decode(token, options={"verify_signature": False}) except Exception as e: - raise ApiError( - error_code="bad_id_token", - message="Cannot decode id_token", - status_code=401, - ) from e - print(f"decoded_token: {decoded_token}") - print(f"cls.service_url(): {cls.server_url()}") - # import pdb; pdb.set_trace() + raise TokenInvalidError('Cannot decode token') from e + if decoded_token["iss"] != cls.server_url(): valid = False elif ( @@ -159,15 +162,10 @@ class AuthenticationService: valid = False if not valid: - current_app.logger.error(f"Invalid token in validate_id_token: {id_token}") return False if now > decoded_token["exp"]: - raise ApiError( - error_code="invalid_token", - message="Your token is expired. Please Login", - status_code=401, - ) + raise TokenExpiredError("Your token is expired. Please Login") return True diff --git a/spiffworkflow-frontend/package-lock.json b/spiffworkflow-frontend/package-lock.json index 0d174daf0..8466f7a8d 100644 --- a/spiffworkflow-frontend/package-lock.json +++ b/spiffworkflow-frontend/package-lock.json @@ -39,6 +39,7 @@ "bpmn-js": "^9.3.2", "bpmn-js-properties-panel": "^1.10.0", "bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#main", + "cookie": "^0.5.0", "craco": "^0.0.3", "date-fns": "^2.28.0", "diagram-js": "^8.5.0", @@ -66,6 +67,7 @@ }, "devDependencies": { "@cypress/grep": "^3.1.0", + "@types/cookie": "^0.5.1", "@typescript-eslint/eslint-plugin": "^5.30.5", "@typescript-eslint/parser": "^5.30.6", "cypress": "^12", @@ -5654,6 +5656,12 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.1.tgz", + "integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==", + "dev": true + }, "node_modules/@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -35330,6 +35338,12 @@ "@types/node": "*" } }, + "@types/cookie": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.1.tgz", + "integrity": "sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==", + "dev": true + }, "@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", diff --git a/spiffworkflow-frontend/package.json b/spiffworkflow-frontend/package.json index 8b65b9450..515d191a7 100644 --- a/spiffworkflow-frontend/package.json +++ b/spiffworkflow-frontend/package.json @@ -34,6 +34,7 @@ "bpmn-js": "^9.3.2", "bpmn-js-properties-panel": "^1.10.0", "bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#main", + "cookie": "^0.5.0", "craco": "^0.0.3", "date-fns": "^2.28.0", "diagram-js": "^8.5.0", @@ -102,6 +103,7 @@ }, "devDependencies": { "@cypress/grep": "^3.1.0", + "@types/cookie": "^0.5.1", "@typescript-eslint/eslint-plugin": "^5.30.5", "@typescript-eslint/parser": "^5.30.6", "cypress": "^12", diff --git a/spiffworkflow-frontend/src/index.tsx b/spiffworkflow-frontend/src/index.tsx index 7a602be60..42eddc027 100644 --- a/spiffworkflow-frontend/src/index.tsx +++ b/spiffworkflow-frontend/src/index.tsx @@ -20,7 +20,7 @@ const doRender = () => { ); }; -UserService.getAuthTokenFromParams(); +UserService.loginIfNeeded(); doRender(); // If you want to start measuring performance in your app, pass a function diff --git a/spiffworkflow-frontend/src/routes/AuthenticationList.tsx b/spiffworkflow-frontend/src/routes/AuthenticationList.tsx index a0d151018..3fdb748be 100644 --- a/spiffworkflow-frontend/src/routes/AuthenticationList.tsx +++ b/spiffworkflow-frontend/src/routes/AuthenticationList.tsx @@ -42,7 +42,7 @@ export default function AuthenticationList() { row.id }?redirect_url=${redirectUrl}/${ row.id - }?token=${UserService.getAuthToken()}`} + }?token=${UserService.getAccessToken()}`} > {row.id} diff --git a/spiffworkflow-frontend/src/routes/ProcessGroupList.tsx b/spiffworkflow-frontend/src/routes/ProcessGroupList.tsx index 4c0f5d9ab..5ba8563a0 100644 --- a/spiffworkflow-frontend/src/routes/ProcessGroupList.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessGroupList.tsx @@ -72,7 +72,6 @@ export default function ProcessGroupList() { console.log('document.cookie', document.cookie); return ( <> - {document.cookie}