22import { Ability , Subject , subject } from '@casl/ability'
33import Debug from 'debug'
44import { each , forIn , get , isEmpty , isEqual , omit , set } from 'lodash'
5- import {
6- Acl ,
7- AclAction ,
8- OpenAPIDoc ,
9- PermissionSchema ,
10- Schema ,
11- SessionUser ,
12- TeamAuthz ,
13- UserAuthz ,
14- } from 'src/otomi-models'
5+ import { Acl , AclAction , OpenAPIDoc , Schema , SessionUser , TeamAuthz , UserAuthz } from 'src/otomi-models'
156import OtomiStack from 'src/otomi-stack'
167import { extract , flattenObject } from 'src/utils'
178
@@ -72,9 +63,6 @@ export function isValidAuthzSpec(apiDoc: OpenAPIDoc): boolean {
7263 const { schemas } = apiDoc . components
7364 forIn ( schemas , ( schema : Schema , schemaName : string ) => {
7465 debug ( `loading rules for ${ schemaName } schema` )
75- // @ts -ignore
76- // eslint-disable-next-line no-param-reassign
77-
7866 if ( schema . type === 'array' ) {
7967 if ( schema [ 'x-acl' ] ) {
8068 err . concat (
@@ -128,7 +116,6 @@ export const loadSpecRules = (apiDoc: OpenAPIDoc): any => {
128116 const { schemas } = apiDoc . components
129117
130118 Object . keys ( schemas ) . forEach ( ( schemaName : string ) => {
131- // debug(`loading rules for ${schemaName} schema`)
132119 const schema : Schema = schemas [ schemaName ]
133120
134121 if ( schema . type === 'array' ) return
@@ -146,9 +133,8 @@ export const loadSpecRules = (apiDoc: OpenAPIDoc): any => {
146133
147134export default class Authz {
148135 user : SessionUser
149-
150136 specRules : Record < string , Schema >
151-
137+ // TODO: replace Ability as it's deprecated
152138 rbac : Ability
153139
154140 constructor ( apiDoc : OpenAPIDoc ) {
@@ -189,10 +175,7 @@ export default class Authz {
189175 // actions like *-any imply that * is also allowed, so exclude those from inversion
190176 const normalized = _actions . map ( ( a ) => ( a . includes ( '-any' ) ? a . slice ( 0 , - 4 ) : a ) )
191177 allowedAttributeCrudActions
192- . filter ( ( a ) => {
193- const cond = ! ( normalized . includes ( a ) || _actions . includes ( `${ a } -any` ) )
194- return cond
195- } )
178+ . filter ( ( a ) => ! ( normalized . includes ( a ) || _actions . includes ( `${ a } -any` ) ) )
196179 . forEach ( createRule ( schemaName , prop , true ) )
197180 if ( obj . properties ) createRules ( `${ schemaName } .${ prop } ` , obj )
198181 } )
@@ -226,19 +209,18 @@ export default class Authz {
226209 // also check if we are denied by lack of self service
227210 const deniedSelfServiceAttributes = get (
228211 this . user . authz ,
229- `${ teamId } .deniedAttributes.${ schemaName . toLowerCase ( ) } ` ,
212+ `${ teamId } .deniedAttributes.teamMembers ` ,
230213 [ ] ,
231214 ) as Array < string >
232- // the two above denied lists should be mutually exclusive, because a schema design should not
233- // have have both self service as well as acl set for the same property, so we can merge the result
215+ // merge denied attributes from both role-based and self-service restrictions
234216 const deniedAttributes = [ ...deniedRoleAttributes , ...deniedSelfServiceAttributes ]
235217
236218 deniedAttributes . forEach ( ( path ) => {
237219 const val = get ( body , path )
238220 const origVal = get ( dataOrig , path )
239- // undefined value expected for forbidden props, just put back before save
221+ // undefined value expected for forbidden props, so put back original before save
240222 if ( val === undefined ) set ( body , path , origVal )
241- // value provided which shouldn't happen
223+ // if a value is provided which is not allowed, mark it as violated
242224 else if ( ! isEqual ( val , origVal ) ) violatedAttributes . push ( path )
243225 } )
244226 return violatedAttributes
@@ -260,32 +242,46 @@ export default class Authz {
260242 return body . length !== undefined ? ret : ret [ 0 ]
261243 }
262244
263- hasSelfService = ( teamId : string , schema , attribute : string ) => {
264- const deniedAttributes = get ( this . user . authz , `${ teamId } .deniedAttributes.${ schema } ` , [ ] ) as Array < string >
265- if ( deniedAttributes . includes ( attribute ) ) return false
266- return true
245+ hasSelfService = ( teamId : string , attribute : string ) : boolean => {
246+ const deniedAttributes = get ( this . user . authz , `${ teamId } .deniedAttributes.teamMembers` , [ ] ) as Array < string >
247+ return ! deniedAttributes . includes ( attribute )
267248 }
268249}
269250
270- export const getTeamSelfServiceAuthz = (
271- teams : Array < string > ,
272- schema : PermissionSchema ,
273- otomi : OtomiStack ,
274- ) : UserAuthz => {
251+ export const getTeamSelfServiceAuthz = ( teams : Array < string > , otomi : OtomiStack ) : UserAuthz => {
275252 const permissionMap : UserAuthz = { }
276253
277254 teams . forEach ( ( teamId ) => {
278- const authz : TeamAuthz = { } as TeamAuthz
279- Object . keys ( schema . properties ) . forEach ( ( propName ) => {
280- const possiblePermissions = schema . properties [ propName ] . items . enum
281- set ( authz , `deniedAttributes.${ propName } ` , [ ] )
282- authz . deniedAttributes [ propName ] = possiblePermissions . filter ( ( name ) => {
283- const flags = get ( otomi . getTeamSelfServiceFlags ( teamId ) , propName , [ ] )
284- return ! flags . includes ( name )
255+ // Initialize the team authorization object.
256+ const authz : TeamAuthz = { deniedAttributes : { } }
257+
258+ // Retrieve the selfService flags for the team.
259+ // Expected shape: { teamMembers: { createServices: boolean, editSecurityPolicies: boolean, ... } }
260+ const selfServiceFlags = otomi . getTeamSelfServiceFlags ( teamId ) ?. teamMembers
261+
262+ // Initialize deniedAttributes for teamMembers as an empty array.
263+ authz . deniedAttributes . teamMembers = [ ]
264+
265+ if ( selfServiceFlags ) {
266+ // For each permission, if its flag is false then add it to the denied list.
267+ Object . entries ( selfServiceFlags ) . forEach ( ( [ permissionName , allowed ] ) => {
268+ if ( ! allowed ) {
269+ authz . deniedAttributes . teamMembers . push ( permissionName )
270+ }
285271 } )
286- if ( propName === 'team' ) authz . deniedAttributes . team . push ( 'selfService' )
287- } )
272+ } else {
273+ // Fallback: if no selfService data is found, deny all permissions.
274+ authz . deniedAttributes . teamMembers = [
275+ 'createServices' ,
276+ 'editSecurityPolicies' ,
277+ 'useCloudShell' ,
278+ 'downloadKubeconfig' ,
279+ 'downloadDockerLogin' ,
280+ ]
281+ }
282+
288283 permissionMap [ teamId ] = authz
289284 } )
285+
290286 return permissionMap
291287}
0 commit comments