11/* eslint-disable no-param-reassign */
22import { getSpec } from 'src/app'
33import { debug } from 'console'
4- import { RequestHandler } from 'express'
54import { find } from 'lodash'
65import get from 'lodash/get'
7- import Authz , { getTeamSelfServiceAuthz } from 'src/authz'
6+ import Authz from 'src/authz'
87import { HttpError } from 'src/error'
98import { OpenApiRequestExt } from 'src/otomi-models'
10- import OtomiStack from 'src/otomi-stack'
119import { RepoService } from '../services/RepoService'
12- import { getSessionStack } from './session'
1310
1411const HttpMethodMapping : Record < string , string > = {
1512 DELETE : 'delete' ,
@@ -41,34 +38,29 @@ function renameKeys(obj: Record<string, any>) {
4138// }
4239// }
4340
44- export function authorize ( req : OpenApiRequestExt , res , next , authz : Authz , repoService : RepoService ) : RequestHandler {
45- const { params, query, body, user } = req
41+ /**
42+ * Authorize a request based on RBAC and ABAC rules.
43+ * Called by the security handler.
44+ * Throws HttpError if authorization fails.
45+ */
46+ export function authorize ( req : OpenApiRequestExt , authz : Authz , repoService : RepoService ) : void {
47+ const { body, user } = req
4648 // express-openapi-validator stores path params in req.openapi.pathParams
47- const teamId = req . openapi ?. pathParams ?. teamId ?? params ?. teamId ?? query ?. teamId ?? body ?. teamId
49+ const teamId = req . openapi ?. pathParams ?. teamId ?? req . params ?. teamId ?? req . query ?. teamId ?? body ?. teamId
4850 const action = HttpMethodMapping [ req . method ]
4951
50- // express-openapi-validator doesn't expose custom x- extensions on req.openapi.schema
51- // so we need to look it up from the loaded spec using the route path
52- const apiSpec = getSpec ( ) . spec
53- const routePath = ( req . openapi as any ) ?. openApiRoute || req . path
54- const method = req . method . toLowerCase ( )
55-
56- console . log ( 'DEBUG authz - routePath:' , routePath , 'method:' , method )
57- console . log ( 'DEBUG authz - Looking for x-aclSchema at:' , `paths['${ routePath } ']['${ method } ']['x-aclSchema']` )
58- console . log ( 'DEBUG authz - Available paths:' , Object . keys ( apiSpec . paths || { } ) )
59-
60- const schema : string =
61- get ( apiSpec , `paths['${ routePath } ']['${ method } ']['x-aclSchema']` , '' ) || get ( req , 'operationDoc.x-aclSchema' , '' )
62- const schemaName = schema . split ( '/' ) . pop ( ) || null
52+ // Get x-aclSchema from req.openapi.schema (set by express-openapi-validator)
53+ const schemaName = ( req . openapi ?. schema as any ) ?. [ 'x-aclSchema' ] || null
6354
64- console . log ( 'DEBUG authz - Found schema:' , schema , 'schemaName:' , schemaName )
55+ // If there is no RBAC then we allow the request
56+ if ( ! schemaName ) return
6557
66- // If there is no RBAC then we bail
67- if ( ! schemaName ) return next ( )
58+ const apiSpec = getSpec ( ) . spec
6859
69- // init rules for the user
60+ // Initialize rules for the user
7061 authz . init ( user )
7162
63+ // Check RBAC permissions
7264 let valid
7365 const isTeamMember = ! user . isPlatformAdmin && user . teams . includes ( teamId )
7466 if ( isTeamMember ) {
@@ -81,12 +73,15 @@ export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, repoS
8173 const key = `${ action } :${ schemaName } `
8274 const permission = permissionMap [ key ]
8375 valid = permission ? authz . hasSelfService ( teamId , permission ) : authz . validateWithCasl ( action , schemaName , teamId )
84- } else valid = authz . validateWithCasl ( action , schemaName , teamId )
76+ } else {
77+ valid = authz . validateWithCasl ( action , schemaName , teamId )
78+ }
8579
8680 if ( ! valid ) {
8781 throw new HttpError ( 403 , `User not allowed to perform "${ action } " on "${ schemaName } " resource` )
8882 }
8983
84+ // Check ABAC permissions for create/update operations
9085 const schemaToRepoMap : Record < string , string > = {
9186 Service : 'services' ,
9287 Team : 'teamConfig' ,
@@ -98,11 +93,9 @@ export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, repoS
9893 Policy : 'policies' ,
9994 SealedSecret : 'sealedSecrets' ,
10095 }
101- const teamSpecificCollections = [ 'builds' , 'services' , 'workloads' , 'netpols' , 'policies' , 'sealedSecrets' ] // <-- These are fetched per team
96+ const teamSpecificCollections = [ 'builds' , 'services' , 'workloads' , 'netpols' , 'policies' , 'sealedSecrets' ]
10297
103- // Use openapi.pathParams if available, fallback to params for backward compatibility
104- const pathParams = req . openapi ?. pathParams ?? params
105- const selector = renameKeys ( pathParams )
98+ //TODO lookup if we can remove this
10699 const collectionId = schemaToRepoMap [ schemaName ]
107100 if ( collectionId && [ 'create' , 'update' ] . includes ( action ) ) {
108101 // Look up x-allow-values from the API spec for ABAC validation
@@ -114,6 +107,8 @@ export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, repoS
114107
115108 if ( action === 'update' ) {
116109 try {
110+ const pathParams = req . openapi ?. pathParams ?? req . params
111+ const selector = renameKeys ( pathParams )
117112 let collection
118113 if ( teamSpecificCollections . includes ( collectionId ) ) {
119114 collection = repoService . getTeamConfigService ( teamId ) . getCollection ( collectionId )
@@ -122,41 +117,16 @@ export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, repoS
122117 }
123118 dataOrig = find ( collection , selector ) || { }
124119 } catch ( error ) {
125- debug ( 'Error in authzMiddleware ' , error )
120+ debug ( 'Error in authorize ' , error )
126121 }
127122 }
128123
129124 const violatedAttributes = authz . validateWithAbac ( action , schemaName , teamId , req . body , dataOrig )
130125 if ( violatedAttributes . length > 0 ) {
131- return res . status ( 403 ) . send ( {
132- authz : false ,
133- message : `User not allowed to modify the following attributes ${ violatedAttributes } " of ${ schemaName } " resource` ,
134- } )
135- }
136- }
137- // TODO: enable next part later:
138- //filter response based on abac
139- // res.json = wrapResponse(
140- // (obj: Record<string, any>) => authz.filterWithAbac(schemaName, teamId, obj),
141- // res.json.bind(res),
142- // )
143-
144- return next ( )
145- }
146- export function authzMiddleware ( authz : Authz ) : RequestHandler {
147- return async function nextHandler ( req : OpenApiRequestExt , res , next ) : Promise < any > {
148- if ( req . user ) {
149- req . isSecurityHandler = true
150- } else {
151- return next ( )
152- }
153- try {
154- const otomi : OtomiStack = await getSessionStack ( req . user . email )
155- // Now we call the new helper which derives authz based on the new selfService.teamMembers flags.
156- req . user . authz = getTeamSelfServiceAuthz ( req . user . teams , otomi )
157- return authorize ( req , res , next , authz , otomi . repoService )
158- } catch ( error ) {
159- return next ( error )
126+ throw new HttpError (
127+ 403 ,
128+ `User not allowed to modify the following attributes: ${ violatedAttributes . join ( ', ' ) } of ${ schemaName } resource` ,
129+ )
160130 }
161131 }
162132}
0 commit comments