mirror of
https://github.com/sartography/spiff-arena.git
synced 2025-03-01 01:20:45 +00:00
allow disabling authentication from the backend w/ burnettk
This commit is contained in:
parent
01ec63f71a
commit
3f0f06817f
@ -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;
|
||||
'
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
)
|
||||
|
@ -120,6 +120,8 @@ export default function NavigationBar() {
|
||||
<a target="_blank" href={documentationUrl} rel="noreferrer">
|
||||
Documentation
|
||||
</a>
|
||||
{!UserService.authenticationDisabled() ? (
|
||||
<>
|
||||
<hr />
|
||||
<Button
|
||||
data-qa="logout-button"
|
||||
@ -129,6 +131,8 @@ export default function NavigationBar() {
|
||||
<Logout />
|
||||
Sign out
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</ToggletipContent>
|
||||
</Toggletip>
|
||||
</div>
|
||||
|
@ -62,6 +62,15 @@ const getUserEmail = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const authenticationDisabled = () => {
|
||||
const idToken = getIdToken();
|
||||
if (idToken) {
|
||||
const idObject = jwt(idToken);
|
||||
return (idObject as any).authentication_disabled;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const getPreferredUsername = () => {
|
||||
const idToken = getIdToken();
|
||||
if (idToken) {
|
||||
@ -82,14 +91,15 @@ const hasRole = (_roles: string[]): boolean => {
|
||||
};
|
||||
|
||||
const UserService = {
|
||||
authenticationDisabled,
|
||||
doLogin,
|
||||
doLogout,
|
||||
isLoggedIn,
|
||||
getAccessToken,
|
||||
loginIfNeeded,
|
||||
getPreferredUsername,
|
||||
getUserEmail,
|
||||
hasRole,
|
||||
isLoggedIn,
|
||||
loginIfNeeded,
|
||||
};
|
||||
|
||||
export default UserService;
|
||||
|
Loading…
x
Reference in New Issue
Block a user