@@ -134,3 +134,242 @@ describe('Schema collection wise permissions', () => {
134134 sessionTeam . authz = { }
135135 } )
136136} )
137+
138+ describe ( 'Self-service permissions' , ( ) => {
139+ const spec : OpenAPIDoc = {
140+ components : {
141+ schemas : {
142+ Kubecfg : { type : 'object' , 'x-acl' : { teamMember : [ 'read' ] } , properties : { } } ,
143+ DockerConfig : { type : 'object' , 'x-acl' : { teamMember : [ 'read' ] } , properties : { } } ,
144+ Cloudtty : { type : 'object' , 'x-acl' : { teamMember : [ 'create' ] } , properties : { } } ,
145+ Policy : { type : 'object' , 'x-acl' : { teamMember : [ 'update' ] } , properties : { } } ,
146+ } ,
147+ } ,
148+ paths : { } ,
149+ security : [ ] ,
150+ }
151+
152+ const teamId = 'mercury'
153+ let authz : Authz
154+ beforeEach ( ( ) => {
155+ authz = new Authz ( spec ) . init ( { ...sessionTeam , teams : [ teamId ] } )
156+ } )
157+
158+ test ( 'Team member can download kubeconfig (self-service)' , ( ) => {
159+ expect ( authz . hasSelfService ( teamId , 'downloadKubeconfig' ) ) . toBeDefined ( )
160+ } )
161+ test ( 'Team member can download docker login (self-service)' , ( ) => {
162+ expect ( authz . hasSelfService ( teamId , 'downloadDockerLogin' ) ) . toBeDefined ( )
163+ } )
164+ test ( 'Team member can use cloud shell (self-service)' , ( ) => {
165+ expect ( authz . hasSelfService ( teamId , 'useCloudShell' ) ) . toBeDefined ( )
166+ } )
167+ test ( 'Team member can edit security policies (self-service)' , ( ) => {
168+ expect ( authz . hasSelfService ( teamId , 'editSecurityPolicies' ) ) . toBeDefined ( )
169+ } )
170+ test ( 'Team member cannot use undefined self-service permission' , ( ) => {
171+ expect ( authz . hasSelfService ( teamId , 'notARealPermission' ) ) . toBeDefined ( )
172+ } )
173+ } )
174+
175+ describe ( 'Authz middleware cases' , ( ) => {
176+ const spec : OpenAPIDoc = {
177+ components : { schemas : { Service : { type : 'object' , 'x-acl' : { teamMember : [ 'read' ] } , properties : { } } } } ,
178+ paths : { } ,
179+ security : [ ] ,
180+ }
181+ let authz : Authz
182+ beforeEach ( ( ) => {
183+ authz = new Authz ( spec ) . init ( sessionTeam )
184+ } )
185+
186+ test ( 'Returns false if user is not in team' , ( ) => {
187+ expect ( authz . validateWithCasl ( 'read' , 'Service' , 'notMyTeam' ) ) . toBe ( false )
188+ } )
189+ test ( 'Returns true if user is in team' , ( ) => {
190+ expect ( authz . validateWithCasl ( 'read' , 'Service' , 'mercury' ) ) . toBe ( true )
191+ } )
192+ test ( 'Returns false if schema is missing' , ( ) => {
193+ expect ( authz . validateWithCasl ( 'read' , '' , 'mercury' ) ) . toBe ( false )
194+ } )
195+ test ( 'Returns false if action is not allowed' , ( ) => {
196+ expect ( authz . validateWithCasl ( 'delete' , 'Service' , 'mercury' ) ) . toBe ( false )
197+ } )
198+ } )
199+
200+ describe ( 'Platform admin, team admin and team member scenarios' , ( ) => {
201+ const spec : OpenAPIDoc = {
202+ components : {
203+ schemas : {
204+ Resource : {
205+ type : 'object' ,
206+ 'x-acl' : {
207+ platformAdmin : [ 'create-any' , 'read-any' , 'update-any' , 'delete-any' ] ,
208+ teamAdmin : [ 'create' , 'read' , 'update' , 'delete' ] ,
209+ teamMember : [ 'read' ] ,
210+ } ,
211+ properties : { } ,
212+ } ,
213+ } ,
214+ } ,
215+ paths : { } ,
216+ security : [ ] ,
217+ }
218+ const platformAdmin : SessionUser = { ...sessionTeam , isPlatformAdmin : true , roles : [ SessionRole . PlatformAdmin ] }
219+ const teamAdmin : SessionUser = { ...sessionTeam , isTeamAdmin : true , roles : [ SessionRole . TeamAdmin ] }
220+ const myTeam = 'mercury'
221+ const otherTeam = 'venus'
222+
223+ test ( 'Platform admin can CRUD any resource' , ( ) => {
224+ const authz = new Authz ( spec ) . init ( platformAdmin )
225+ expect ( authz . validateWithCasl ( 'create' , 'Resource' , 'anyTeam' ) ) . toBe ( true )
226+ expect ( authz . validateWithCasl ( 'read' , 'Resource' , 'anyTeam' ) ) . toBe ( true )
227+ expect ( authz . validateWithCasl ( 'update' , 'Resource' , 'anyTeam' ) ) . toBe ( true )
228+ expect ( authz . validateWithCasl ( 'delete' , 'Resource' , 'anyTeam' ) ) . toBe ( true )
229+ } )
230+ test ( 'Team admin can CRUD resources in their team' , ( ) => {
231+ const authz = new Authz ( spec ) . init ( teamAdmin )
232+ expect ( authz . validateWithCasl ( 'create' , 'Resource' , myTeam ) ) . toBe ( true )
233+ expect ( authz . validateWithCasl ( 'read' , 'Resource' , myTeam ) ) . toBe ( true )
234+ expect ( authz . validateWithCasl ( 'update' , 'Resource' , myTeam ) ) . toBe ( true )
235+ expect ( authz . validateWithCasl ( 'delete' , 'Resource' , myTeam ) ) . toBe ( true )
236+ } )
237+ test ( 'Team admin cannot CRUD resources in other teams' , ( ) => {
238+ const authz = new Authz ( spec ) . init ( teamAdmin )
239+ expect ( authz . validateWithCasl ( 'create' , 'Resource' , otherTeam ) ) . toBe ( false )
240+ expect ( authz . validateWithCasl ( 'read' , 'Resource' , otherTeam ) ) . toBe ( false )
241+ expect ( authz . validateWithCasl ( 'update' , 'Resource' , otherTeam ) ) . toBe ( false )
242+ expect ( authz . validateWithCasl ( 'delete' , 'Resource' , otherTeam ) ) . toBe ( false )
243+ } )
244+ test ( 'Team member can Read resources' , ( ) => {
245+ const authz = new Authz ( spec ) . init ( sessionTeam )
246+ expect ( authz . validateWithCasl ( 'read' , 'Resource' , myTeam ) ) . toBe ( true )
247+ } )
248+ test ( 'Team member cannot CUD resources' , ( ) => {
249+ const authz = new Authz ( spec ) . init ( sessionTeam )
250+ expect ( authz . validateWithCasl ( 'create' , 'Resource' , myTeam ) ) . toBe ( false )
251+ expect ( authz . validateWithCasl ( 'update' , 'Resource' , myTeam ) ) . toBe ( false )
252+ expect ( authz . validateWithCasl ( 'delete' , 'Resource' , myTeam ) ) . toBe ( false )
253+ } )
254+ test ( 'Team member cannot CRUD resources in other teams' , ( ) => {
255+ const authz = new Authz ( spec ) . init ( sessionTeam )
256+ expect ( authz . validateWithCasl ( 'create' , 'Resource' , otherTeam ) ) . toBe ( false )
257+ expect ( authz . validateWithCasl ( 'read' , 'Resource' , otherTeam ) ) . toBe ( false )
258+ expect ( authz . validateWithCasl ( 'update' , 'Resource' , otherTeam ) ) . toBe ( false )
259+ expect ( authz . validateWithCasl ( 'delete' , 'Resource' , otherTeam ) ) . toBe ( false )
260+ } )
261+ test ( 'Platform admin can perform self-service actions' , ( ) => {
262+ const authz = new Authz ( spec ) . init ( platformAdmin )
263+ expect ( authz . hasSelfService ( 'anyTeam' , 'downloadKubeconfig' ) ) . toBeDefined ( )
264+ expect ( authz . hasSelfService ( 'anyTeam' , 'downloadDockerLogin' ) ) . toBeDefined ( )
265+ expect ( authz . hasSelfService ( 'anyTeam' , 'useCloudShell' ) ) . toBeDefined ( )
266+ expect ( authz . hasSelfService ( 'anyTeam' , 'editSecurityPolicies' ) ) . toBeDefined ( )
267+ } )
268+ } )
269+
270+ describe ( 'ABAC attribute denial' , ( ) => {
271+ const spec : OpenAPIDoc = {
272+ components : {
273+ schemas : {
274+ Team : { type : 'object' , 'x-acl' : { teamMember : [ 'update' ] } , properties : { } } ,
275+ } ,
276+ } ,
277+ paths : { } ,
278+ security : [ ] ,
279+ }
280+ const teamId = 'teamA'
281+ let authz : Authz
282+ beforeEach ( ( ) => {
283+ authz = new Authz ( spec ) . init ( sessionTeam )
284+ sessionTeam . authz = { [ teamId ] : { deniedAttributes : { Team : [ 'foo' , 'bar' ] } } }
285+ } )
286+ test ( 'Denied attributes are respected' , ( ) => {
287+ expect ( ( ) => authz . hasSelfService ( teamId , 'foo' ) ) . not . toThrow ( )
288+ expect ( ( ) => authz . hasSelfService ( teamId , 'bar' ) ) . not . toThrow ( )
289+ } )
290+ test ( 'Allowed attribute is not denied' , ( ) => {
291+ expect ( ( ) => authz . hasSelfService ( teamId , 'baz' ) ) . not . toThrow ( )
292+ } )
293+ afterEach ( ( ) => {
294+ sessionTeam . authz = { }
295+ } )
296+ } )
297+
298+ describe ( 'Fallback to CASL when no self-service permission' , ( ) => {
299+ const spec : OpenAPIDoc = {
300+ components : {
301+ schemas : {
302+ App : { type : 'object' , 'x-acl' : { teamMember : [ 'read' ] } , properties : { } } ,
303+ } ,
304+ } ,
305+ paths : { } ,
306+ security : [ ] ,
307+ }
308+ let authz : Authz
309+ beforeEach ( ( ) => {
310+ authz = new Authz ( spec ) . init ( sessionTeam )
311+ } )
312+ test ( 'Falls back to CASL for non-self-service action' , ( ) => {
313+ expect ( authz . validateWithCasl ( 'read' , 'App' , 'mercury' ) ) . toBe ( true )
314+ } )
315+ test ( 'Returns false for denied action' , ( ) => {
316+ expect ( authz . validateWithCasl ( 'delete' , 'App' , 'mercury' ) ) . toBe ( false )
317+ } )
318+ } )
319+
320+ describe ( 'Team member self-service vs. other team resources' , ( ) => {
321+ const spec : OpenAPIDoc = {
322+ components : {
323+ schemas : {
324+ Service : {
325+ type : 'object' ,
326+ 'x-acl' : {
327+ teamMember : [ 'read' , 'update' , 'delete' , 'create' ] ,
328+ } ,
329+ properties : {
330+ name : { type : 'string' } ,
331+ teamId : { type : 'string' } ,
332+ } ,
333+ } ,
334+ } ,
335+ } ,
336+ paths : { } ,
337+ security : [ ] ,
338+ }
339+ const myTeam = 'mercury'
340+ const otherTeam = 'venus'
341+ let authz : Authz
342+ beforeEach ( ( ) => {
343+ authz = new Authz ( spec ) . init ( { ...sessionTeam , teams : [ myTeam ] } )
344+ } )
345+
346+ test ( 'Team member can CRUD own team resource' , ( ) => {
347+ expect ( authz . validateWithCasl ( 'create' , 'Service' , myTeam ) ) . toBe ( true )
348+ expect ( authz . validateWithCasl ( 'read' , 'Service' , myTeam ) ) . toBe ( true )
349+ expect ( authz . validateWithCasl ( 'update' , 'Service' , myTeam ) ) . toBe ( true )
350+ expect ( authz . validateWithCasl ( 'delete' , 'Service' , myTeam ) ) . toBe ( true )
351+ } )
352+
353+ test ( 'Team member cannot CRUD another team resource' , ( ) => {
354+ expect ( authz . validateWithCasl ( 'create' , 'Service' , otherTeam ) ) . toBe ( false )
355+ expect ( authz . validateWithCasl ( 'read' , 'Service' , otherTeam ) ) . toBe ( false )
356+ expect ( authz . validateWithCasl ( 'update' , 'Service' , otherTeam ) ) . toBe ( false )
357+ expect ( authz . validateWithCasl ( 'delete' , 'Service' , otherTeam ) ) . toBe ( false )
358+ } )
359+
360+ test ( 'Team member with no self-service permission cannot perform custom self-service action' , ( ) => {
361+ sessionTeam . authz = { [ myTeam ] : { deniedAttributes : { Policy : [ 'editSecurityPolicies' ] } } }
362+ expect ( ( ) => authz . hasSelfService ( myTeam , 'editSecurityPolicies' ) ) . not . toThrow ( )
363+ sessionTeam . authz = { }
364+ } )
365+
366+ test ( 'Team member with no self-service permission cannot perform custom self-service action in another team' , ( ) => {
367+ sessionTeam . authz = { [ otherTeam ] : { deniedAttributes : { Policy : [ 'editSecurityPolicies' ] } } }
368+ expect ( ( ) => authz . hasSelfService ( myTeam , 'editSecurityPolicies' ) ) . not . toThrow ( )
369+ sessionTeam . authz = { }
370+ } )
371+
372+ test ( 'Team member with self-service permission can perform allowed self-service action' , ( ) => {
373+ expect ( ( ) => authz . hasSelfService ( myTeam , 'read' ) ) . not . toThrow ( )
374+ } )
375+ } )
0 commit comments