@@ -180,7 +180,11 @@ import * as grpc from "@grpc/grpc-js";
180
180
import { CachingBlobServiceClientProvider } from "../util/content-service-sugar" ;
181
181
import { CostCenterJSON } from "@gitpod/gitpod-protocol/lib/usage" ;
182
182
import { createCookielessId , maskIp } from "../analytics" ;
183
- import { ConfigCatClientFactory } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server" ;
183
+ import {
184
+ ConfigCatClientFactory ,
185
+ getExperimentsClientForBackend ,
186
+ } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server" ;
187
+ import { OrganizationOperation } from "../authorization/perms" ;
184
188
185
189
// shortcut
186
190
export const traceWI = ( ctx : TraceContext , wi : Omit < LogContext , "userId" > ) => TraceContext . setOWI ( ctx , wi ) ; // userId is already taken care of in WebsocketConnectionManager
@@ -2033,11 +2037,26 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2033
2037
protected async guardTeamOperation (
2034
2038
teamId : string ,
2035
2039
op : ResourceAccessOp ,
2040
+ fineGrainedOps : OrganizationOperation [ ] ,
2036
2041
) : Promise < { team : Team ; members : TeamMemberInfo [ ] } > {
2037
2042
if ( ! uuidValidate ( teamId ) ) {
2038
2043
throw new ResponseError ( ErrorCodes . BAD_REQUEST , "organization ID must be a valid UUID" ) ;
2039
2044
}
2040
2045
2046
+ const user = this . checkUser ( ) ;
2047
+ const centralizedPermissionsEnabled = await getExperimentsClientForBackend ( ) . getValueAsync (
2048
+ "centralizedPermissions" ,
2049
+ false ,
2050
+ {
2051
+ user : user ,
2052
+ teamId : teamId ,
2053
+ } ,
2054
+ ) ;
2055
+
2056
+ if ( centralizedPermissionsEnabled ) {
2057
+ log . info ( "[perms] Checking team operations." , { org : teamId , operations : fineGrainedOps , user : user . id } ) ;
2058
+ }
2059
+
2041
2060
const team = await this . teamDB . findTeamById ( teamId ) ;
2042
2061
if ( ! team ) {
2043
2062
// We return Permission Denied because we don't want to leak the existence, or not of the Organization.
@@ -2060,15 +2079,15 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2060
2079
2061
2080
this . checkAndBlockUser ( "getTeam" ) ;
2062
2081
2063
- const { team } = await this . guardTeamOperation ( teamId , "get" ) ;
2082
+ const { team } = await this . guardTeamOperation ( teamId , "get" , [ "org_members_read" ] ) ;
2064
2083
return team ;
2065
2084
}
2066
2085
2067
2086
public async updateTeam ( ctx : TraceContext , teamId : string , team : Pick < Team , "name" > ) : Promise < Team > {
2068
2087
traceAPIParams ( ctx , { teamId } ) ;
2069
2088
this . checkUser ( "updateTeam" ) ;
2070
2089
2071
- await this . guardTeamOperation ( teamId , "update" ) ;
2090
+ await this . guardTeamOperation ( teamId , "update" , [ "org_write" ] ) ;
2072
2091
2073
2092
const updatedTeam = await this . teamDB . updateTeam ( teamId , team ) ;
2074
2093
return updatedTeam ;
@@ -2077,7 +2096,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2077
2096
public async getTeamMembers ( ctx : TraceContext , teamId : string ) : Promise < TeamMemberInfo [ ] > {
2078
2097
traceAPIParams ( ctx , { teamId } ) ;
2079
2098
2080
- const { members } = await this . guardTeamOperation ( teamId , "get" ) ;
2099
+ const { members } = await this . guardTeamOperation ( teamId , "get" , [ "org_members_read" ] ) ;
2081
2100
2082
2101
return members ;
2083
2102
}
@@ -2148,7 +2167,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2148
2167
}
2149
2168
2150
2169
this . checkAndBlockUser ( "setTeamMemberRole" ) ;
2151
- await this . guardTeamOperation ( teamId , "update" ) ;
2170
+ await this . guardTeamOperation ( teamId , "update" , [ "org_members_write" ] ) ;
2152
2171
2153
2172
await this . teamDB . setTeamMemberRole ( userId , teamId , role ) ;
2154
2173
}
@@ -2161,8 +2180,15 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2161
2180
}
2162
2181
2163
2182
const user = this . checkAndBlockUser ( "removeTeamMember" ) ;
2164
- // Users are free to leave any team themselves, but only owners can remove others from their teams.
2165
- await this . guardTeamOperation ( teamId , user . id === userId ? "get" : "update" ) ;
2183
+ // The user is leaving a team, if they are removing themselves from the team.
2184
+ const userLeavingTeam = user . id === userId ;
2185
+
2186
+ if ( userLeavingTeam ) {
2187
+ await this . guardTeamOperation ( teamId , "update" , [ "not_implemented" ] ) ;
2188
+ } else {
2189
+ await this . guardTeamOperation ( teamId , "get" , [ "org_members_write" ] ) ;
2190
+ }
2191
+
2166
2192
const membership = await this . teamDB . findTeamMembership ( userId , teamId ) ;
2167
2193
if ( ! membership ) {
2168
2194
throw new Error ( `Could not find membership for user '${ userId } ' in organization '${ teamId } '` ) ;
@@ -2183,7 +2209,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2183
2209
traceAPIParams ( ctx , { teamId } ) ;
2184
2210
2185
2211
this . checkUser ( "getGenericInvite" ) ;
2186
- await this . guardTeamOperation ( teamId , "get" ) ;
2212
+ await this . guardTeamOperation ( teamId , "get" , [ "org_members_write" ] ) ;
2187
2213
2188
2214
const invite = await this . teamDB . findGenericInviteByTeamId ( teamId ) ;
2189
2215
if ( invite ) {
@@ -2196,7 +2222,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2196
2222
traceAPIParams ( ctx , { teamId } ) ;
2197
2223
2198
2224
this . checkAndBlockUser ( "resetGenericInvite" ) ;
2199
- await this . guardTeamOperation ( teamId , "update" ) ;
2225
+ await this . guardTeamOperation ( teamId , "update" , [ "org_members_write" ] ) ;
2200
2226
return this . teamDB . resetGenericInvite ( teamId ) ;
2201
2227
}
2202
2228
@@ -2213,7 +2239,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2213
2239
}
2214
2240
} else {
2215
2241
// Anyone who can read a team's information (i.e. any team member) can manage team projects
2216
- await this . guardTeamOperation ( project . teamId || "" , "get" ) ;
2242
+ await this . guardTeamOperation ( project . teamId || "" , "get" , [ "not_implemented" ] ) ;
2217
2243
}
2218
2244
}
2219
2245
@@ -2240,7 +2266,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2240
2266
}
2241
2267
} else {
2242
2268
// Anyone who can read a team's information (i.e. any team member) can create a new project.
2243
- await this . guardTeamOperation ( params . teamId || "" , "get" ) ;
2269
+ await this . guardTeamOperation ( params . teamId || "" , "get" , [ "not_implemented" ] ) ;
2244
2270
}
2245
2271
2246
2272
return this . projectsService . createProject ( params , user ) ;
@@ -2265,7 +2291,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2265
2291
const user = this . checkAndBlockUser ( "deleteTeam" ) ;
2266
2292
traceAPIParams ( ctx , { teamId, userId : user . id } ) ;
2267
2293
2268
- await this . guardTeamOperation ( teamId , "delete" ) ;
2294
+ await this . guardTeamOperation ( teamId , "delete" , [ "org_write" ] ) ;
2269
2295
2270
2296
const teamProjects = await this . projectsService . getTeamProjects ( teamId ) ;
2271
2297
teamProjects . forEach ( ( project ) => {
@@ -2298,7 +2324,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2298
2324
2299
2325
this . checkUser ( "getTeamProjects" ) ;
2300
2326
2301
- await this . guardTeamOperation ( teamId , "get" ) ;
2327
+ await this . guardTeamOperation ( teamId , "get" , [ "not_implemented" ] ) ;
2302
2328
return this . projectsService . getTeamProjects ( teamId ) ;
2303
2329
}
2304
2330
@@ -2921,7 +2947,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2921
2947
}
2922
2948
2923
2949
// Ensure user can perform this operation on this organization
2924
- await this . guardTeamOperation ( newProvider . organizationId , "create" ) ;
2950
+ await this . guardTeamOperation ( newProvider . organizationId , "create" , [ "org_write" ] ) ;
2925
2951
2926
2952
try {
2927
2953
// on creating we're are checking for already existing runtime providers
@@ -2969,7 +2995,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2969
2995
2970
2996
await this . guardWithFeatureFlag ( "orgGitAuthProviders" , providerUpdate . organizationId ) ;
2971
2997
2972
- await this . guardTeamOperation ( providerUpdate . organizationId , "update" ) ;
2998
+ await this . guardTeamOperation ( providerUpdate . organizationId , "update" , [ "org_write" ] ) ;
2973
2999
2974
3000
try {
2975
3001
const result = await this . authProviderService . updateOrgAuthProvider ( providerUpdate ) ;
@@ -2990,7 +3016,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
2990
3016
2991
3017
await this . guardWithFeatureFlag ( "orgGitAuthProviders" , params . organizationId ) ;
2992
3018
2993
- await this . guardTeamOperation ( params . organizationId , "get" ) ;
3019
+ await this . guardTeamOperation ( params . organizationId , "get" , [ "org_read" ] ) ;
2994
3020
2995
3021
try {
2996
3022
const result = await this . authProviderService . getAuthProvidersOfOrg ( params . organizationId ) ;
@@ -3021,7 +3047,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
3021
3047
throw new ResponseError ( ErrorCodes . NOT_FOUND , "Provider resource not found." ) ;
3022
3048
}
3023
3049
3024
- await this . guardTeamOperation ( authProvider . organizationId || "" , "delete" ) ;
3050
+ await this . guardTeamOperation ( authProvider . organizationId || "" , "delete" , [ "org_write" ] ) ;
3025
3051
3026
3052
try {
3027
3053
await this . authProviderService . deleteAuthProvider ( authProvider ) ;
0 commit comments