6
6
7
7
import { v1 } from "@authzed/authzed-node" ;
8
8
9
- import { Organization , Project , TeamMemberInfo , TeamMemberRole } from "@gitpod/gitpod-protocol" ;
9
+ import { Organization , Project , TeamMemberInfo , TeamMemberRole , User } from "@gitpod/gitpod-protocol" ;
10
10
import { ApplicationError , ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error" ;
11
11
import {
12
12
InstallationID ,
13
+ InstallationRelation ,
13
14
OrganizationPermission ,
15
+ OrganizationRelation ,
14
16
Permission ,
15
17
ProjectPermission ,
18
+ ProjectRelation ,
16
19
Relation ,
17
20
ResourceType ,
18
21
UserPermission ,
22
+ UserRelation ,
19
23
} from "./definitions" ;
20
24
import { SpiceDBAuthorizer } from "./spicedb-authorizer" ;
21
25
import { BUILTIN_INSTLLATION_ADMIN_USER_ID } from "@gitpod/gitpod-db/lib" ;
22
26
23
27
export function createInitializingAuthorizer ( spiceDbAuthorizer : SpiceDBAuthorizer ) : Authorizer {
24
28
const target = new Authorizer ( spiceDbAuthorizer ) ;
25
- const initialized = target . addAdminRole ( BUILTIN_INSTLLATION_ADMIN_USER_ID ) ;
29
+ const initialized = target . addInstallationAdminRole ( BUILTIN_INSTLLATION_ADMIN_USER_ID ) ;
26
30
return new Proxy ( target , {
27
31
get ( target , propKey , receiver ) {
28
32
const originalMethod = target [ propKey as keyof typeof target ] ;
@@ -39,6 +43,24 @@ export function createInitializingAuthorizer(spiceDbAuthorizer: SpiceDBAuthorize
39
43
} ) ;
40
44
}
41
45
46
+ export const installation = {
47
+ type : "installation" ,
48
+ id : InstallationID ,
49
+ } ;
50
+
51
+ export type Resource = typeof installation | User | Organization | Project ;
52
+ export namespace Resource {
53
+ export function getType ( res : Resource ) : ResourceType {
54
+ return ( res as any ) . type === "installation"
55
+ ? "installation"
56
+ : User . is ( res )
57
+ ? "user"
58
+ : Project . is ( res )
59
+ ? "project"
60
+ : "organization" ;
61
+ }
62
+ }
63
+
42
64
export class Authorizer {
43
65
constructor ( private authorizer : SpiceDBAuthorizer ) { }
44
66
@@ -125,15 +147,52 @@ export class Authorizer {
125
147
126
148
// write operations below
127
149
128
- async addUser ( userId : string ) {
150
+ public async removeAllRelationships ( type : ResourceType , id : string ) {
151
+ await this . authorizer . deleteRelationships (
152
+ v1 . DeleteRelationshipsRequest . create ( {
153
+ relationshipFilter : {
154
+ resourceType : type ,
155
+ optionalResourceId : id ,
156
+ } ,
157
+ } ) ,
158
+ ) ;
159
+
160
+ // iterate over all resource types and remove by subject
161
+ for ( const resourcetype of [ "installation" , "user" , "organization" , "project" ] as ResourceType [ ] ) {
162
+ await this . authorizer . deleteRelationships (
163
+ v1 . DeleteRelationshipsRequest . create ( {
164
+ relationshipFilter : {
165
+ resourceType : resourcetype ,
166
+ optionalResourceId : "" ,
167
+ optionalRelation : "" ,
168
+ optionalSubjectFilter : {
169
+ subjectType : type ,
170
+ optionalSubjectId : id ,
171
+ } ,
172
+ } ,
173
+ } ) ,
174
+ ) ;
175
+ }
176
+ }
177
+
178
+ async addUser ( userId : string , owningOrgId ?: string ) {
179
+ const updates = [
180
+ v1 . RelationshipUpdate . create ( {
181
+ operation : v1 . RelationshipUpdate_Operation . TOUCH ,
182
+ relationship : relationship ( objectRef ( "user" , userId ) , "self" , subject ( "user" , userId ) ) ,
183
+ } ) ,
184
+ v1 . RelationshipUpdate . create ( {
185
+ operation : v1 . RelationshipUpdate_Operation . TOUCH ,
186
+ relationship : relationship (
187
+ objectRef ( "user" , userId ) ,
188
+ "container" ,
189
+ owningOrgId ? subject ( "organization" , owningOrgId ) : subject ( "installation" , InstallationID ) ,
190
+ ) ,
191
+ } ) ,
192
+ ] ;
129
193
await this . authorizer . writeRelationships (
130
194
v1 . WriteRelationshipsRequest . create ( {
131
- updates : [
132
- v1 . RelationshipUpdate . create ( {
133
- operation : v1 . RelationshipUpdate_Operation . TOUCH ,
134
- relationship : relationship ( objectRef ( "user" , userId ) , "self" , subject ( "user" , userId ) ) ,
135
- } ) ,
136
- ] ,
195
+ updates,
137
196
} ) ,
138
197
) ;
139
198
}
@@ -197,17 +256,6 @@ export class Authorizer {
197
256
) ;
198
257
}
199
258
200
- async deleteOrganization ( orgID : string ) : Promise < void > {
201
- await this . authorizer . deleteRelationships (
202
- v1 . DeleteRelationshipsRequest . create ( {
203
- relationshipFilter : v1 . RelationshipFilter . create ( {
204
- resourceType : "organization" ,
205
- optionalResourceId : orgID ,
206
- } ) ,
207
- } ) ,
208
- ) ;
209
- }
210
-
211
259
async addOrganization ( org : Organization , members : TeamMemberInfo [ ] , projects : Project [ ] ) : Promise < void > {
212
260
const updates : v1 . RelationshipUpdate [ ] = [ ] ;
213
261
@@ -272,7 +320,7 @@ export class Authorizer {
272
320
) ;
273
321
}
274
322
275
- async addAdminRole ( userID : string ) {
323
+ async addInstallationAdminRole ( userID : string ) {
276
324
await this . authorizer . writeRelationships (
277
325
v1 . WriteRelationshipsRequest . create ( {
278
326
updates : [
@@ -375,6 +423,76 @@ export class Authorizer {
375
423
} ) ,
376
424
] ;
377
425
}
426
+
427
+ public async readRelationships (
428
+ inst : typeof installation ,
429
+ relation : InstallationRelation ,
430
+ target : Resource ,
431
+ ) : Promise < Relationship [ ] > ;
432
+ public async readRelationships ( user : User , relation : UserRelation , target : Resource ) : Promise < Relationship [ ] > ;
433
+ public async readRelationships (
434
+ org : Organization ,
435
+ relation : OrganizationRelation ,
436
+ target : Resource ,
437
+ ) : Promise < Relationship [ ] > ;
438
+ public async readRelationships (
439
+ project : Project ,
440
+ relation : ProjectRelation ,
441
+ target : Resource ,
442
+ ) : Promise < Relationship [ ] > ;
443
+ public async readRelationships ( subject : Resource , relation ?: Relation , object ?: Resource ) : Promise < Relationship [ ] > ;
444
+ public async readRelationships ( subject : Resource , relation ?: Relation , object ?: Resource ) : Promise < Relationship [ ] > {
445
+ const relationShips = await this . authorizer . readRelationships ( {
446
+ consistency : v1 . Consistency . create ( {
447
+ requirement : {
448
+ oneofKind : "fullyConsistent" ,
449
+ fullyConsistent : true ,
450
+ } ,
451
+ } ) ,
452
+ relationshipFilter : {
453
+ resourceType : Resource . getType ( subject ) ,
454
+ optionalResourceId : subject . id ,
455
+ optionalRelation : relation || "" ,
456
+ optionalSubjectFilter : object && {
457
+ subjectType : Resource . getType ( object ) ,
458
+ optionalSubjectId : object ?. id ,
459
+ } ,
460
+ } ,
461
+ } ) ;
462
+ return relationShips
463
+ . map ( ( rel ) => {
464
+ const subject = rel . relationship ?. subject ?. object ;
465
+ const object = rel . relationship ?. resource ;
466
+ const relation = rel . relationship ?. relation ;
467
+ if ( ! subject || ! object || ! relation ) {
468
+ throw new Error ( "Invalid relationship" ) ;
469
+ }
470
+ return new Relationship (
471
+ object . objectType as ResourceType ,
472
+ object . objectId ! ,
473
+ relation as Relation ,
474
+ subject . objectType as ResourceType ,
475
+ subject . objectId ! ,
476
+ ) ;
477
+ } )
478
+ . sort ( ( a , b ) => {
479
+ return a . toString ( ) . localeCompare ( b . toString ( ) ) ;
480
+ } ) ;
481
+ }
482
+ }
483
+
484
+ export class Relationship {
485
+ constructor (
486
+ public readonly subjectType : ResourceType ,
487
+ public readonly subjectID : string ,
488
+ public readonly relation : Relation ,
489
+ public readonly objectType : ResourceType ,
490
+ public readonly objectID : string ,
491
+ ) { }
492
+
493
+ public toString ( ) : string {
494
+ return `${ this . subjectType } :${ this . subjectID } #${ this . relation } @${ this . objectType } :${ this . objectID } ` ;
495
+ }
378
496
}
379
497
380
498
function objectRef ( type : ResourceType , id : string ) : v1 . ObjectReference {
0 commit comments