1+ import { isTeamOwner } from "@calcom/features/ee/teams/lib/queries" ;
2+ import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations" ;
3+ import { prisma } from "@calcom/prisma" ;
4+ import type { Membership } from "@calcom/prisma/client" ;
5+ import { MembershipRole } from "@calcom/prisma/enums" ;
6+
7+ import { RoleManagementError , RoleManagementErrorCode } from "../domain/errors/role-management.error" ;
8+ import type { IRoleManager } from "./role-manager.interface" ;
9+
10+ export class LegacyRoleManager implements IRoleManager {
11+ public isPBACEnabled = false ;
12+
13+ protected async validateRoleChange (
14+ userId : number ,
15+ teamId : number ,
16+ memberId : number ,
17+ newRole : MembershipRole | string ,
18+ memberships : Membership [ ]
19+ ) : Promise < void > {
20+ // Only validate for traditional MembershipRole values
21+ if ( typeof newRole !== "string" || ! Object . values ( MembershipRole ) . includes ( newRole as MembershipRole ) ) {
22+ return ;
23+ }
24+
25+ const targetMembership = memberships . find ( ( m ) => m . userId === memberId ) ;
26+ const myMembership = memberships . find ( ( m ) => m . userId === userId ) ;
27+ const teamOwners = memberships . filter ( ( m ) => m . role === MembershipRole . OWNER ) ;
28+ const teamHasMoreThanOneOwner = teamOwners . length > 1 ;
29+
30+ if ( ! targetMembership ) {
31+ throw new RoleManagementError ( "Target membership not found" , RoleManagementErrorCode . UNAUTHORIZED ) ;
32+ }
33+
34+ // Only owners can award owner role
35+ if ( newRole === MembershipRole . OWNER && ! ( await isTeamOwner ( userId , teamId ) ) ) {
36+ throw new RoleManagementError ( "Only owners can award owner role" , RoleManagementErrorCode . UNAUTHORIZED ) ;
37+ }
38+
39+ // Admins cannot change the role of an owner
40+ if ( myMembership ?. role === MembershipRole . ADMIN && targetMembership ?. role === MembershipRole . OWNER ) {
41+ throw new RoleManagementError (
42+ "You can not change the role of an owner if you are an admin." ,
43+ RoleManagementErrorCode . UNAUTHORIZED
44+ ) ;
45+ }
46+
47+ // Cannot change the role of the only owner
48+ if ( targetMembership ?. role === MembershipRole . OWNER && ! teamHasMoreThanOneOwner ) {
49+ throw new RoleManagementError (
50+ "You can not change the role of the only owner of a team." ,
51+ RoleManagementErrorCode . UNAUTHORIZED
52+ ) ;
53+ }
54+
55+ // Admins cannot promote themselves to a higher role (except to MEMBER which is a demotion)
56+ if (
57+ myMembership ?. role === MembershipRole . ADMIN &&
58+ memberId === userId &&
59+ newRole !== MembershipRole . MEMBER
60+ ) {
61+ throw new RoleManagementError (
62+ "You can not change yourself to a higher role." ,
63+ RoleManagementErrorCode . UNAUTHORIZED
64+ ) ;
65+ }
66+ }
67+
68+ async checkPermissionToChangeRole (
69+ userId : number ,
70+ targetId : number ,
71+ scope : "org" | "team" ,
72+ memberId ?: number ,
73+ newRole ?: MembershipRole | string
74+ ) : Promise < void > {
75+ let hasPermission = false ;
76+ if ( scope === "team" ) {
77+ const team = await prisma . membership . findFirst ( {
78+ where : {
79+ userId,
80+ teamId : targetId ,
81+ accepted : true ,
82+ OR : [ { role : "ADMIN" } , { role : "OWNER" } ] ,
83+ } ,
84+ } ) ;
85+ hasPermission = ! ! team ;
86+ } else {
87+ hasPermission = ! ! ( await isOrganisationAdmin ( userId , targetId ) ) ;
88+ }
89+
90+ // Only OWNER/ADMIN can update role
91+ if ( ! hasPermission ) {
92+ throw new RoleManagementError (
93+ "Only owners or admin can update roles" ,
94+ RoleManagementErrorCode . UNAUTHORIZED
95+ ) ;
96+ }
97+
98+ // Additional validation for team role changes in legacy mode
99+ if ( scope === "team" && memberId && newRole ) {
100+ const memberships = await prisma . membership . findMany ( {
101+ where : {
102+ teamId : targetId ,
103+ accepted : true ,
104+ } ,
105+ } ) ;
106+ await this . validateRoleChange ( userId , targetId , memberId , newRole , memberships ) ;
107+ }
108+ }
109+
110+ async assignRole (
111+ userId : number ,
112+ organizationId : number ,
113+ role : MembershipRole | string ,
114+ // Used in other implementation
115+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
116+ _membershipId : number
117+ ) : Promise < void > {
118+ await prisma . membership . update ( {
119+ where : {
120+ userId_teamId : {
121+ userId,
122+ teamId : organizationId ,
123+ } ,
124+ } ,
125+ data : {
126+ role : role as MembershipRole ,
127+ } ,
128+ } ) ;
129+ }
130+
131+ // Used in other implementation
132+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
133+ async getAllRoles ( _organizationId : number ) : Promise < { id : string ; name : string } [ ] > {
134+ return [
135+ { id : MembershipRole . OWNER , name : "Owner" } ,
136+ { id : MembershipRole . ADMIN , name : "Admin" } ,
137+ { id : MembershipRole . MEMBER , name : "Member" } ,
138+ ] ;
139+ }
140+
141+ // Used in other implementation
142+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
143+ async getTeamRoles ( _teamId : number ) : Promise < { id : string ; name : string } [ ] > {
144+ return [
145+ { id : MembershipRole . OWNER , name : "Owner" } ,
146+ { id : MembershipRole . ADMIN , name : "Admin" } ,
147+ { id : MembershipRole . MEMBER , name : "Member" } ,
148+ ] ;
149+ }
150+ }
0 commit comments