diff --git a/spiffworkflow-backend/bin/get_perms b/spiffworkflow-backend/bin/get_perms index 9199efbc3..5f561b014 100755 --- a/spiffworkflow-backend/bin/get_perms +++ b/spiffworkflow-backend/bin/get_perms @@ -14,13 +14,13 @@ fi # shellcheck disable=2016 mysql -uroot "$database" -e ' - select u.username user, g.identifier group + select u.username username, g.identifier group_name FROM `user` u - JOIN `user_group_assignment` uga on uga.user_id = u.id - JOIN `group` g on g.id = uga.group_id; + JOIN `user_group_assignment` uga ON uga.user_id = u.id + JOIN `group` g ON g.id = uga.group_id; select pa.id, g.identifier group_identifier, pt.uri, permission from permission_assignment pa - join principal p on p.id = pa.principal_id - join `group` g on g.id = p.group_id - join permission_target pt on pt.id = pa.permission_target_id; + JOIN principal p ON p.id = pa.principal_id + JOIN `group` g ON g.id = p.group_id + JOIN permission_target pt ON pt.id = pa.permission_target_id; ' diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py index 25cbbab2e..63f1ec7b3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/default.py @@ -79,6 +79,10 @@ SPIFFWORKFLOW_BACKEND_OPEN_ID_TENANT_SPECIFIC_FIELDS = environ.get( "SPIFFWORKFLOW_BACKEND_OPEN_ID_TENANT_SPECIFIC_FIELDS" ) +SPIFFWORKFLOW_BACKEND_AUTHENTICATION_DISABLED = ( + environ.get("SPIFFWORKFLOW_BACKEND_AUTHENTICATION_DISABLED", default="false") == "true" +) + # loggers to use is a comma separated list of logger prefixes that we will be converted to list of strings SPIFFWORKFLOW_BACKEND_LOGGERS_TO_USE = environ.get("SPIFFWORKFLOW_BACKEND_LOGGERS_TO_USE") diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py index 18ae20203..f4136f82d 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/group.py @@ -15,13 +15,14 @@ if TYPE_CHECKING: from spiffworkflow_backend.models.user import UserModel # noqa: F401 +SPIFF_NO_AUTH_ANONYMOUS_GROUP = "spiff_anonymous_group" + + class GroupNotFoundError(Exception): - """GroupNotFoundError.""" + pass class GroupModel(SpiffworkflowBaseDBModel): - """GroupModel.""" - __tablename__ = "group" __table_args__ = {"extend_existing": True} diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/principal.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/principal.py index 6e46def5b..31d4ccda7 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/principal.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/principal.py @@ -32,3 +32,5 @@ class PrincipalModel(SpiffworkflowBaseDBModel): user = relationship("UserModel", viewonly=True) group = relationship("GroupModel", viewonly=True) + + permission_assignments = relationship("PermissionAssignmentModel", cascade="delete") # type: ignore diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py index 1f667e0a3..ad68faac9 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/user.py @@ -15,14 +15,15 @@ from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel from spiffworkflow_backend.models.group import GroupModel +SPIFF_NO_AUTH_ANONYMOUS_USER = "spiff_anonymous_user" + + class UserNotFoundError(Exception): - """UserNotFoundError.""" + pass @dataclass class UserModel(SpiffworkflowBaseDBModel): - """UserModel.""" - __tablename__ = "user" __table_args__ = (db.UniqueConstraint("service", "service_id", name="service_key"),) @@ -47,9 +48,9 @@ class UserModel(SpiffworkflowBaseDBModel): secondary="user_group_assignment", overlaps="user_group_assignments,users", ) - principal = relationship("PrincipalModel", uselist=False) # type: ignore + principal = relationship("PrincipalModel", uselist=False, cascade="delete") # type: ignore - def encode_auth_token(self) -> str: + def encode_auth_token(self, extra_payload: dict | None = None) -> str: """Generate the Auth Token. :return: string @@ -59,12 +60,16 @@ class UserModel(SpiffworkflowBaseDBModel): raise KeyError("we need current_app.config to have a SECRET_KEY") # hours = float(app.config['TOKEN_AUTH_TTL_HOURS']) - payload = { - # 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=hours, minutes=0, seconds=0), - # 'iat': datetime.datetime.utcnow(), + base_payload = { + "email": self.email, + "preferred_username": self.username, "sub": f"service:{self.service}::service_id:{self.service_id}", "token_type": "internal", } + + payload = base_payload + if extra_payload is not None: + payload = {**base_payload, **extra_payload} return jwt.encode( payload, secret_key, diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py index e1fc02eb5..134c360f8 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/user.py @@ -6,7 +6,6 @@ import re from typing import Any from typing import Dict from typing import Optional -from typing import Union import flask import jwt @@ -20,6 +19,10 @@ from werkzeug.wrappers import Response from spiffworkflow_backend.exceptions.api_error import ApiError from spiffworkflow_backend.helpers.api_version import V1_API_PATH_PREFIX +from spiffworkflow_backend.models.db import db +from spiffworkflow_backend.models.group import GroupModel +from spiffworkflow_backend.models.group import SPIFF_NO_AUTH_ANONYMOUS_GROUP +from spiffworkflow_backend.models.user import SPIFF_NO_AUTH_ANONYMOUS_USER from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.services.authentication_service import AuthenticationService from spiffworkflow_backend.services.authentication_service import ( @@ -27,6 +30,7 @@ from spiffworkflow_backend.services.authentication_service import ( ) from spiffworkflow_backend.services.authentication_service import TokenExpiredError from spiffworkflow_backend.services.authorization_service import AuthorizationService +from spiffworkflow_backend.services.group_service import GroupService from spiffworkflow_backend.services.user_service import UserService """ @@ -36,9 +40,7 @@ from spiffworkflow_backend.services.user_service import UserService # authorization_exclusion_list = ['status'] -def verify_token( - token: Optional[str] = None, force_run: Optional[bool] = False -) -> Optional[Dict[str, Optional[Union[str, int]]]]: +def verify_token(token: Optional[str] = None, force_run: Optional[bool] = False) -> None: """Verify the token for the user (if provided). If in production environment and token is not provided, gets user from the SSO headers and returns their token. @@ -82,6 +84,22 @@ def verify_token( current_app.logger.error( f"Exception in verify_token getting user from decoded internal token. {e}" ) + + # if the user is the anonymous user and we have auth enabled then make sure we clean up the anonymouse user + if ( + user_model + and not current_app.config.get("SPIFFWORKFLOW_BACKEND_AUTHENTICATION_DISABLED") + and user_model.username == SPIFF_NO_AUTH_ANONYMOUS_USER + and user_model.service_id == "spiff_anonymous_service_id" + ): + group_model = GroupModel.query.filter_by(identifier=SPIFF_NO_AUTH_ANONYMOUS_GROUP).first() + db.session.delete(group_model) + db.session.delete(user_model) + db.session.commit() + tld = current_app.config["THREAD_LOCAL_DATA"] + tld.user_has_logged_out = True + return None + elif "iss" in decoded_token.keys(): user_info = None try: @@ -196,29 +214,22 @@ def set_new_access_token_in_cookie( return response -def encode_auth_token(sub: str, token_type: Optional[str] = None) -> str: - """Generates the Auth Token. - - :return: string - """ - payload = {"sub": sub} - if token_type is None: - token_type = "internal" # noqa: S105 - payload["token_type"] = token_type - if "SECRET_KEY" in current_app.config: - secret_key = current_app.config.get("SECRET_KEY") - else: - current_app.logger.error("Missing SECRET_KEY in encode_auth_token") - raise ApiError(error_code="encode_error", message="Missing SECRET_KEY in encode_auth_token") - return jwt.encode( - payload, - str(secret_key), - algorithm="HS256", - ) - - def login(redirect_url: str = "/") -> Response: - """Login.""" + if current_app.config.get("SPIFFWORKFLOW_BACKEND_AUTHENTICATION_DISABLED"): + user = UserModel.query.filter_by(username=SPIFF_NO_AUTH_ANONYMOUS_USER).first() + if user is None: + user = UserService.create_user( + SPIFF_NO_AUTH_ANONYMOUS_USER, "spiff_anonymous_service", "spiff_anonymous_service_id" + ) + GroupService.add_user_to_group_or_add_to_waiting(user.username, SPIFF_NO_AUTH_ANONYMOUS_GROUP) + AuthorizationService.add_permission_from_uri_or_macro(SPIFF_NO_AUTH_ANONYMOUS_GROUP, "all", "/*") + g.user = user + g.token = user.encode_auth_token({"authentication_disabled": True}) + tld = current_app.config["THREAD_LOCAL_DATA"] + tld.new_access_token = g.token + tld.new_id_token = g.token + return redirect(redirect_url) + state = AuthenticationService.generate_state(redirect_url) login_redirect_url = AuthenticationService().get_login_redirect_url(state.decode("UTF-8")) return redirect(login_redirect_url) diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py index 2ddc861af..3cca074db 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/user_service.py @@ -33,7 +33,6 @@ class UserService: tenant_specific_field_2: Optional[str] = None, tenant_specific_field_3: Optional[str] = None, ) -> UserModel: - """Create_user.""" user_model: Optional[UserModel] = ( UserModel.query.filter(UserModel.service == service).filter(UserModel.service_id == service_id).first() ) diff --git a/spiffworkflow-frontend/src/components/NavigationBar.tsx b/spiffworkflow-frontend/src/components/NavigationBar.tsx index b9301d9fc..f532d3106 100644 --- a/spiffworkflow-frontend/src/components/NavigationBar.tsx +++ b/spiffworkflow-frontend/src/components/NavigationBar.tsx @@ -120,15 +120,19 @@ export default function NavigationBar() { Documentation -