diff --git a/api/chalicelib/core/users.py b/api/chalicelib/core/users.py index 9af070fc5..b8b3e9898 100644 --- a/api/chalicelib/core/users.py +++ b/api/chalicelib/core/users.py @@ -252,9 +252,8 @@ def generate_new_api_key(user_id): cur.mogrify( f"""UPDATE public.users SET api_key=generate_api_key(20) - WHERE - users.user_id = %(userId)s - AND deleted_at IS NULL + WHERE users.user_id = %(userId)s + AND deleted_at IS NULL RETURNING api_key;""", {"userId": user_id}) ) @@ -295,6 +294,39 @@ def edit(user_id_to_update, tenant_id, changes: schemas.EditUserSchema, editor_i return {"data": user} +def edit_member(user_id_to_update, tenant_id, changes: schemas.EditUserSchema, editor_id): + user = get_member(user_id=user_id_to_update, tenant_id=tenant_id) + if editor_id != user_id_to_update or changes.admin is not None and changes.admin != user["admin"]: + admin = get(tenant_id=tenant_id, user_id=editor_id) + if not admin["superAdmin"] and not admin["admin"]: + return {"errors": ["unauthorized"]} + _changes = {} + if editor_id == user_id_to_update: + if changes.admin is not None: + if user["superAdmin"]: + changes.admin = None + elif changes.admin != user["admin"]: + return {"errors": ["cannot change your own role"]} + + if changes.email is not None and changes.email != user["email"]: + if email_exists(changes.email): + return {"errors": ["email already exists."]} + if get_deleted_user_by_email(changes.email) is not None: + return {"errors": ["email previously deleted."]} + _changes["email"] = changes.email + + if changes.name is not None and len(changes.name) > 0: + _changes["name"] = changes.name + + if changes.admin is not None: + _changes["role"] = "admin" if changes.admin else "member" + + if len(_changes.keys()) > 0: + update(tenant_id=tenant_id, user_id=user_id_to_update, changes=_changes) + return {"data": get_member(user_id=user_id_to_update, tenant_id=tenant_id)} + return {"data": user} + + def get_by_email_only(email): with pg_client.PostgresClient() as cur: cur.execute( @@ -342,11 +374,42 @@ def get_by_email_reset(email, reset_token): return helper.dict_to_camel_case(r) +def get_member(tenant_id, user_id): + with pg_client.PostgresClient() as cur: + cur.execute(cur.mogrify( + f"""SELECT + users.user_id, + users.email, + users.role, + users.name, + users.created_at, + (CASE WHEN users.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, + (CASE WHEN users.role = 'admin' THEN TRUE ELSE FALSE END) AS admin, + (CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member, + DATE_PART('day',timezone('utc'::text, now()) \ + - COALESCE(basic_authentication.invited_at,'2000-01-01'::timestamp ))>=1 AS expired_invitation, + basic_authentication.password IS NOT NULL AS joined, + invitation_token + FROM public.users LEFT JOIN public.basic_authentication ON users.user_id=basic_authentication.user_id + WHERE users.deleted_at IS NULL AND users.user_id=%(user_id)s + ORDER BY name, user_id""", {"user_id": user_id}) + ) + u = helper.dict_to_camel_case(cur.fetchone()) + if u: + u["createdAt"] = TimeUTC.datetime_to_timestamp(u["createdAt"]) + if u["invitationToken"]: + u["invitationLink"] = __get_invitation_link(u.pop("invitationToken")) + else: + u["invitationLink"] = None + + return u + + def get_members(tenant_id): with pg_client.PostgresClient() as cur: cur.execute( f"""SELECT - users.user_id AS id, + users.user_id, users.email, users.role, users.name, @@ -360,7 +423,7 @@ def get_members(tenant_id): invitation_token FROM public.users LEFT JOIN public.basic_authentication ON users.user_id=basic_authentication.user_id WHERE users.deleted_at IS NULL - ORDER BY name, id""" + ORDER BY name, user_id""" ) r = cur.fetchall() if len(r): diff --git a/api/routers/core_dynamic.py b/api/routers/core_dynamic.py index 2695a6b09..7bb02461a 100644 --- a/api/routers/core_dynamic.py +++ b/api/routers/core_dynamic.py @@ -140,7 +140,7 @@ def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema = @app.post('/client/members/{memberId}', tags=["client"]) def edit_member(memberId: int, data: schemas.EditMemberSchema, context: schemas.CurrentContext = Depends(OR_context)): - return users.edit(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data, + return users.edit_member(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data, user_id_to_update=memberId) diff --git a/ee/api/chalicelib/core/users.py b/ee/api/chalicelib/core/users.py index 661194bbb..f533fa698 100644 --- a/ee/api/chalicelib/core/users.py +++ b/ee/api/chalicelib/core/users.py @@ -293,9 +293,8 @@ def generate_new_api_key(user_id): cur.mogrify( f"""UPDATE public.users SET api_key=generate_api_key(20) - WHERE - users.user_id = %(userId)s - AND deleted_at IS NULL + WHERE users.user_id = %(userId)s + AND deleted_at IS NULL RETURNING api_key;""", {"userId": user_id}) ) @@ -344,6 +343,47 @@ def edit(user_id_to_update, tenant_id, changes: schemas_ee.EditUserSchema, edito return {"data": user} +def edit_member(user_id_to_update, tenant_id, changes: schemas_ee.EditUserSchema, editor_id): + user = get_member(user_id=user_id_to_update, tenant_id=tenant_id) + if editor_id != user_id_to_update or changes.admin is not None and changes.admin != user["admin"]: + admin = get(tenant_id=tenant_id, user_id=editor_id) + if not admin["superAdmin"] and not admin["admin"]: + return {"errors": ["unauthorized"]} + _changes = {} + if editor_id == user_id_to_update: + if changes.admin is not None: + if user["superAdmin"]: + changes.admin = None + elif changes.admin != user["admin"]: + return {"errors": ["cannot change your own role"]} + if changes.roleId is not None: + if user["superAdmin"]: + changes.roleId = None + elif changes.roleId != user["roleId"]: + return {"errors": ["cannot change your own role"]} + + if changes.email is not None and changes.email != user["email"]: + if email_exists(changes.email): + return {"errors": ["email already exists."]} + if get_deleted_user_by_email(changes.email) is not None: + return {"errors": ["email previously deleted."]} + _changes["email"] = changes.email + + if changes.name is not None and len(changes.name) > 0: + _changes["name"] = changes.name + + if changes.admin is not None: + _changes["role"] = "admin" if changes.admin else "member" + + if changes.roleId is not None: + _changes["roleId"] = changes.roleId + + if len(_changes.keys()) > 0: + update(tenant_id=tenant_id, user_id=user_id_to_update, changes=_changes) + return {"data": get_member(tenant_id=tenant_id, user_id=user_id_to_update)} + return {"data": user} + + def get_by_email_only(email): with pg_client.PostgresClient() as cur: cur.execute( @@ -393,12 +433,49 @@ def get_by_email_reset(email, reset_token): return helper.dict_to_camel_case(r) +def get_member(tenant_id, user_id): + with pg_client.PostgresClient() as cur: + cur.execute( + cur.mogrify( + f"""SELECT + users.user_id, + users.email, + users.role, + users.name, + users.created_at, + (CASE WHEN users.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, + (CASE WHEN users.role = 'admin' THEN TRUE ELSE FALSE END) AS admin, + (CASE WHEN users.role = 'member' THEN TRUE ELSE FALSE END) AS member, + DATE_PART('day',timezone('utc'::text, now()) \ + - COALESCE(basic_authentication.invited_at,'2000-01-01'::timestamp ))>=1 AS expired_invitation, + basic_authentication.password IS NOT NULL OR users.origin IS NOT NULL AS joined, + invitation_token, + role_id, + roles.name AS role_name + FROM public.users + LEFT JOIN public.basic_authentication ON users.user_id=basic_authentication.user_id + LEFT JOIN public.roles USING (role_id) + WHERE users.tenant_id = %(tenant_id)s AND users.deleted_at IS NULL AND users.user_id = %(user_id)s + ORDER BY name, user_id""", + {"tenant_id": tenant_id, "user_id": user_id}) + ) + u = helper.dict_to_camel_case(cur.fetchone()) + if u: + u["createdAt"] = TimeUTC.datetime_to_timestamp(u["createdAt"]) + if u["invitationToken"]: + u["invitationLink"] = __get_invitation_link(u.pop("invitationToken")) + else: + u["invitationLink"] = None + + return u + + def get_members(tenant_id): with pg_client.PostgresClient() as cur: cur.execute( cur.mogrify( f"""SELECT - users.user_id AS id, + users.user_id, users.email, users.role, users.name, @@ -416,7 +493,7 @@ def get_members(tenant_id): LEFT JOIN public.basic_authentication ON users.user_id=basic_authentication.user_id LEFT JOIN public.roles USING (role_id) WHERE users.tenant_id = %(tenant_id)s AND users.deleted_at IS NULL - ORDER BY name, id""", + ORDER BY name, user_id""", {"tenant_id": tenant_id}) ) r = cur.fetchall() diff --git a/ee/api/routers/core_dynamic.py b/ee/api/routers/core_dynamic.py index 176896ebb..b3dac897d 100644 --- a/ee/api/routers/core_dynamic.py +++ b/ee/api/routers/core_dynamic.py @@ -144,8 +144,8 @@ def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema = @app.post('/client/members/{memberId}', tags=["client"]) def edit_member(memberId: int, data: schemas_ee.EditMemberSchema, context: schemas.CurrentContext = Depends(OR_context)): - return users.edit(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data, - user_id_to_update=memberId) + return users.edit_member(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data, + user_id_to_update=memberId) @app.get('/metadata/session_search', tags=["metadata"])