Reworked user spaces #15 #27

Merged
edraft merged 10 commits from #15_user_spaces2 into dev 2025-05-02 11:32:19 +02:00
6 changed files with 89 additions and 20 deletions
Showing only changes of commit 0073253f8f - Show all commits

View File

@ -9,18 +9,28 @@ class Keycloak:
client: Optional[KeycloakOpenID] = None
admin: Optional[KeycloakAdmin] = None
url: Optional[str] = None
client_id: Optional[str] = None
realm: Optional[str] = None
__client_secret: Optional[str] = None
@classmethod
def init(cls):
cls.url = Environment.get("KEYCLOAK_URL", str)
cls.client_id = Environment.get("KEYCLOAK_CLIENT_ID", str)
cls.realm = Environment.get("KEYCLOAK_REALM", str)
cls.__client_secret = Environment.get("KEYCLOAK_CLIENT_SECRET", str)
cls.client = KeycloakOpenID(
server_url=Environment.get("KEYCLOAK_URL", str),
client_id=Environment.get("KEYCLOAK_CLIENT_ID", str),
realm_name=Environment.get("KEYCLOAK_REALM", str),
client_secret_key=Environment.get("KEYCLOAK_CLIENT_SECRET", str),
server_url=cls.url,
client_id=cls.client_id,
realm_name=cls.realm,
client_secret_key=cls.__client_secret,
)
connection = KeycloakOpenIDConnection(
server_url=Environment.get("KEYCLOAK_URL", str),
client_id=Environment.get("KEYCLOAK_CLIENT_ID", str),
realm_name=Environment.get("KEYCLOAK_REALM", str),
client_secret_key=Environment.get("KEYCLOAK_CLIENT_SECRET", str),
server_url=cls.url,
client_id=cls.client_id,
realm_name=cls.realm,
client_secret_key=cls.__client_secret,
)
cls.admin = KeycloakAdmin(connection=connection)

View File

@ -1,33 +1,72 @@
from starlette.requests import Request
from starlette.responses import RedirectResponse, JSONResponse
from api.auth.keycloak_client import Keycloak
from api.route import Route
from core.environment import Environment
from core.logger import Logger
from data.schemas.administration.user import User
from data.schemas.administration.user_dao import userDao
from data.schemas.public.user_invitation import UserInvitation
from data.schemas.public.user_invitation_dao import userInvitationDao
from data.schemas.public.user_space_user import UserSpaceUser
from data.schemas.public.user_space_user_dao import userSpaceUserDao
BasePath = f"/invitation"
BasePath = f"/api/invitation"
logger = Logger(__name__)
def _redirect_to_client():
client_urls = Environment.get("CLIENT_URLS", str)
if client_urls is None:
return JSONResponse({"message": "Invitation accepted."})
return RedirectResponse(client_urls.split(",")[0], status_code=303)
@Route.get(f"{BasePath}/accept/{{invitation_id:path}}")
async def accept_invitation(request: Request):
invitation_id = request.path_params["invitation_id"]
user_invitation = await userInvitationDao.find_single_by(
[{UserInvitation.invitation: invitation_id}]
)
if user_invitation is None:
return JSONResponse({"error": "Invitation not found."}, 404)
if user_invitation.deleted:
return _redirect_to_client()
keycloak_user = Keycloak.admin.get_users({"email": user_invitation.email})
if len(keycloak_user) == 0:
redirect_uri = f"{str(request.base_url)}api/invitation/accept/{invitation_id}"
registration_url = f"{Keycloak.url}/realms/{Keycloak.realm}/protocol/openid-connect/auth?prompt=create&client_id={Keycloak.client_id}&response_type=code&scope=openid&redirect_uri={redirect_uri}"
return RedirectResponse(registration_url, status_code=303)
if user_invitation.email not in [
x.email for x in await userDao.find_by([{User.deleted: False}])
]:
user_id = await userDao.create(User(0, keycloak_user[0]["id"]))
await userSpaceUserDao.create(
UserSpaceUser(0, user_invitation.user_space_id, user_id)
)
await userInvitationDao.delete(user_invitation)
return _redirect_to_client()
@Route.get(f"{BasePath}/user-space/accept/{{invitation_id:path}}")
async def accept_user_space_invitation(request: Request):
invitation_id = request.path_params["invitation_id"]
user_space_user = await userSpaceUserDao.find_single_by(
[{UserSpaceUser.invitation: invitation_id}, {UserSpaceUser.deleted: False}]
)
if user_space_user is None:
return JSONResponse({"error": "Invitation not found or already accepted."})
return JSONResponse({"error": "Invitation not found or already accepted."}, 404)
user_space_user.invitation = None
await userSpaceUserDao.update(user_space_user)
client_urls = Environment.get("CLIENT_URLS", str)
if client_urls is None:
return JSONResponse(
{"message": "Invitation accepted, but no client URLs configured."}
)
return RedirectResponse(client_urls.split(",")[0], status_code=303)
return _redirect_to_client()

View File

@ -22,12 +22,12 @@ class UserSettingMutation(MutationABC):
f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
)
.with_input(UserSettingInput, "input")
.with_require_any([], [self._x])
.with_require_any([], [self._check_user])
)
@staticmethod
async def _x(ctx):
return ctx.data.user_id == (await Route.get_user()).id
async def _check_user(ctx):
return ctx.user.id == (await Route.get_user()).id
@staticmethod
async def resolve_change(obj: UserSettingInput, *_):

View File

@ -0,0 +1,10 @@
CREATE UNIQUE INDEX IF NOT EXISTS user_invitations_unique_email
ON public.user_invitations (email)
WHERE email != 'ANONYMOUS';
ALTER TABLE public.user_invitations
ADD CONSTRAINT user_invitations_unique_invitation UNIQUE (invitation);
ALTER TABLE public.user_spaces_users
ADD CONSTRAINT user_spaces_users_unique_invitation UNIQUE (invitation),
ADD CONSTRAINT user_spaces_users_unique_entry UNIQUE (userspaceid, userid);

View File

@ -9,6 +9,7 @@ from core.database.abc.db_model_dao_abc import DbModelDaoABC
from core.logger import Logger
from core.string import first_to_lower
from data.schemas.administration.user_dao import userDao
from data.schemas.public.user_invitation_dao import userInvitationDao
logger = Logger("DataPrivacy")
@ -77,6 +78,14 @@ class DataPrivacyService:
keycloak_id = user.keycloak_id
# Anonymize internal data
user_invitations = await userInvitationDao.find_by(
[{"email": {"equal": user.email}}]
)
for user_invitation in user_invitations:
user_invitation.email = "ANONYMOUS"
user_invitation.deleted = True
await userInvitationDao.update(user_invitation)
await user.anonymize()
await userDao.delete(user)

View File

@ -45,6 +45,7 @@ export class SidebarService {
this.setSelectedUserSpaceIdToLS(value ? value.id : 0);
await this.setElements();
await this.router.navigate(['/admin/urls']);
});
}