@@ -21,6 +21,7 @@ import (
2121 "log/slog"
2222 "os"
2323 "strings"
24+ "sync"
2425
2526 "github.com/casbin/casbin/v3"
2627 gormadapter "github.com/casbin/gorm-adapter/v3"
@@ -34,6 +35,10 @@ import (
3435var _ shared.AccessControl = & casbinRBAC {}
3536var casbinEnforcer * casbin.ContextEnforcer
3637
38+ // protect against concurrent access on shared rbac structures like maps
39+ // in practical terms this means that whenever we call a function of the casbin context enforcer, we wrap the call inside a mutex lock and unlock
40+ var concurrencyMutex sync.RWMutex
41+
3742type casbinRBAC struct {
3843 domain string // scopes this to a specific domain - or organization
3944 enforcer * casbin.ContextEnforcer
@@ -55,7 +60,9 @@ func (c *casbinRBAC) GetExternalEntityProviderID() *string {
5560}
5661
5762func (c * casbinRBAC ) GetOwnerOfOrganization () (string , error ) {
63+ concurrencyMutex .RLock ()
5864 listOfUsers := c .enforcer .GetUsersForRoleInDomain ("role::owner" , "domain::" + c .domain )
65+ concurrencyMutex .RUnlock ()
5966 if len (listOfUsers ) == 0 {
6067 return "" , fmt .Errorf ("no owner found for organization" )
6168 }
@@ -66,7 +73,9 @@ func (c *casbinRBAC) GetOwnerOfOrganization() (string, error) {
6673}
6774
6875func (c * casbinRBAC ) GetAllMembersOfOrganization () ([]string , error ) {
76+ concurrencyMutex .RLock ()
6977 users , err := c .enforcer .GetAllUsersByDomain ("domain::" + c .domain )
78+ concurrencyMutex .RUnlock ()
7079 if err != nil {
7180 return nil , err
7281 }
@@ -78,7 +87,9 @@ func (c *casbinRBAC) GetAllMembersOfOrganization() ([]string, error) {
7887}
7988
8089func (c * casbinRBAC ) GetAllMembersOfProject (projectID string ) ([]string , error ) {
90+ concurrencyMutex .RLock ()
8191 users , err := c .enforcer .GetImplicitUsersForRole ("project::" + projectID + "|role::member" , "domain::" + c .domain )
92+ concurrencyMutex .RUnlock ()
8293 if err != nil {
8394 return nil , err
8495 }
@@ -90,7 +101,9 @@ func (c *casbinRBAC) GetAllMembersOfProject(projectID string) ([]string, error)
90101}
91102
92103func (c * casbinRBAC ) GetAllMembersOfAsset (assetID string ) ([]string , error ) {
104+ concurrencyMutex .RLock ()
93105 users , err := c .enforcer .GetImplicitUsersForRole ("asset::" + assetID + "|role::member" , "domain::" + c .domain )
106+ concurrencyMutex .RUnlock ()
94107 if err != nil {
95108 return nil , err
96109 }
@@ -102,13 +115,17 @@ func (c *casbinRBAC) GetAllMembersOfAsset(assetID string) ([]string, error) {
102115}
103116
104117func (c * casbinRBAC ) HasAccess (ctx context.Context , user string ) (bool , error ) {
118+ concurrencyMutex .RLock ()
105119 roles := c .enforcer .GetRolesForUserInDomain ("user::" + user , "domain::" + c .domain )
120+ concurrencyMutex .RUnlock ()
106121 return len (roles ) > 0 , nil
107122}
108123
109124func (c * casbinRBAC ) GetAllProjectsForUser (user string ) ([]string , error ) {
110125 projectIDs := []string {}
126+ concurrencyMutex .RLock ()
111127 roles , _ := c .enforcer .GetImplicitRolesForUser ("user::" + user , "domain::" + c .domain )
128+ concurrencyMutex .RUnlock ()
112129 for _ , role := range roles {
113130 if ! strings .HasPrefix (role , "project::" ) || ! strings .Contains (role , "role::" ) {
114131 continue
@@ -120,7 +137,9 @@ func (c *casbinRBAC) GetAllProjectsForUser(user string) ([]string, error) {
120137
121138func (c * casbinRBAC ) GetAllAssetsForUser (user string ) ([]string , error ) {
122139 assetIDs := []string {}
140+ concurrencyMutex .RLock ()
123141 roles , _ := c .enforcer .GetImplicitRolesForUser ("user::" + user , "domain::" + c .domain )
142+ concurrencyMutex .RUnlock ()
124143 for _ , role := range roles {
125144 if ! strings .HasPrefix (role , "asset::" ) || ! strings .Contains (role , "role::" ) {
126145 continue
@@ -131,7 +150,9 @@ func (c *casbinRBAC) GetAllAssetsForUser(user string) ([]string, error) {
131150}
132151
133152func (c * casbinRBAC ) GetAllRoles (user string ) []string {
153+ concurrencyMutex .RLock ()
134154 roles , err := c .enforcer .GetImplicitRolesForUser ("user::" + user , "domain::" + c .domain )
155+ concurrencyMutex .RUnlock ()
135156 if err != nil {
136157 slog .Error ("GetAllRoles failed" , "err" , err )
137158 return []string {}
@@ -204,31 +225,43 @@ func (c *casbinRBAC) getAssetRoleName(role shared.Role, asset string) string {
204225}
205226
206227func (c * casbinRBAC ) GrantRole (ctx context.Context , user string , role shared.Role ) error {
228+ concurrencyMutex .Lock ()
229+ defer concurrencyMutex .Unlock ()
207230 _ , err := c .enforcer .AddRoleForUserInDomainCtx (ctx , "user::" + user , "role::" + string (role ), "domain::" + c .domain )
208231 return err
209232}
210233
211234func (c * casbinRBAC ) RevokeRole (ctx context.Context , user string , role shared.Role ) error {
235+ concurrencyMutex .Lock ()
236+ defer concurrencyMutex .Unlock ()
212237 _ , err := c .enforcer .DeleteRoleForUserInDomainCtx (ctx , "user::" + user , "role::" + string (role ), "domain::" + c .domain )
213238 return err
214239}
215240
216241func (c * casbinRBAC ) GrantRoleInProject (ctx context.Context , user string , role shared.Role , project string ) error {
242+ concurrencyMutex .Lock ()
243+ defer concurrencyMutex .Unlock ()
217244 _ , err := c .enforcer .AddRoleForUserInDomainCtx (ctx , "user::" + user , "project::" + project + "|role::" + string (role ), "domain::" + c .domain )
218245 return err
219246}
220247
221248func (c * casbinRBAC ) GrantRoleInAsset (ctx context.Context , user string , role shared.Role , asset string ) error {
249+ concurrencyMutex .Lock ()
250+ defer concurrencyMutex .Unlock ()
222251 _ , err := c .enforcer .AddRoleForUserInDomainCtx (ctx , "user::" + user , "asset::" + asset + "|role::" + string (role ), "domain::" + c .domain )
223252 return err
224253}
225254
226255func (c * casbinRBAC ) RevokeRoleInProject (ctx context.Context , user string , role shared.Role , project string ) error {
256+ concurrencyMutex .Lock ()
257+ defer concurrencyMutex .Unlock ()
227258 _ , err := c .enforcer .DeleteRoleForUserInDomainCtx (ctx , "user::" + user , "project::" + project + "|role::" + string (role ), "domain::" + c .domain )
228259 return err
229260}
230261
231262func (c * casbinRBAC ) RevokeRoleInAsset (ctx context.Context , user string , role shared.Role , project string ) error {
263+ concurrencyMutex .Lock ()
264+ defer concurrencyMutex .Unlock ()
232265 _ , err := c .enforcer .DeleteRoleForUserInDomainCtx (ctx , "user::" + user , "asset::" + project + "|role::" + string (role ), "domain::" + c .domain )
233266 return err
234267}
@@ -252,31 +285,43 @@ func (c *casbinRBAC) RevokeAllRolesInAssetForUser(ctx context.Context, user stri
252285}
253286
254287func (c * casbinRBAC ) InheritRole (ctx context.Context , roleWhichGetsPermissions , roleWhichProvidesPermissions shared.Role ) error {
288+ concurrencyMutex .Lock ()
289+ defer concurrencyMutex .Unlock ()
255290 _ , err := c .enforcer .AddRoleForUserInDomainCtx (ctx , "role::" + string (roleWhichGetsPermissions ), "role::" + string (roleWhichProvidesPermissions ), "domain::" + c .domain )
256291 return err
257292}
258293
259294func (c * casbinRBAC ) InheritProjectRole (ctx context.Context , roleWhichGetsPermissions , roleWhichProvidesPermissions shared.Role , project string ) error {
295+ concurrencyMutex .Lock ()
296+ defer concurrencyMutex .Unlock ()
260297 _ , err := c .enforcer .AddRoleForUserInDomainCtx (ctx , c .getProjectRoleName (roleWhichGetsPermissions , project ), c .getProjectRoleName (roleWhichProvidesPermissions , project ), "domain::" + c .domain )
261298 return err
262299}
263300
264301func (c * casbinRBAC ) InheritAssetRole (ctx context.Context , roleWhichGetsPermissions , roleWhichProvidesPermissions shared.Role , asset string ) error {
302+ concurrencyMutex .Lock ()
303+ defer concurrencyMutex .Unlock ()
265304 _ , err := c .enforcer .AddRoleForUserInDomainCtx (ctx , c .getAssetRoleName (roleWhichGetsPermissions , asset ), c .getAssetRoleName (roleWhichProvidesPermissions , asset ), "domain::" + c .domain )
266305 return err
267306}
268307
269308func (c * casbinRBAC ) InheritProjectRolesAcrossProjects (ctx context.Context , roleWhichGetsPermissions , roleWhichProvidesPermissions shared.ProjectRole ) error {
309+ concurrencyMutex .Lock ()
310+ defer concurrencyMutex .Unlock ()
270311 _ , err := c .enforcer .AddRoleForUserInDomainCtx (ctx , c .getProjectRoleName (roleWhichGetsPermissions .Role , roleWhichGetsPermissions .Project ), c .getProjectRoleName (roleWhichProvidesPermissions .Role , roleWhichProvidesPermissions .Project ), "domain::" + c .domain )
271312 return err
272313}
273314
274315func (c * casbinRBAC ) LinkDomainAndProjectRole (ctx context.Context , domainRoleWhichGetsPermission , projectRoleWhichProvidesPermissions shared.Role , project string ) error {
316+ concurrencyMutex .Lock ()
317+ defer concurrencyMutex .Unlock ()
275318 _ , err := c .enforcer .AddRoleForUserInDomainCtx (ctx , "role::" + string (domainRoleWhichGetsPermission ), c .getProjectRoleName (projectRoleWhichProvidesPermissions , project ), "domain::" + c .domain )
276319 return err
277320}
278321
279322func (c * casbinRBAC ) LinkProjectAndAssetRole (ctx context.Context , projectRoleWhichGetsPermission , assetRoleWhichProvidesPermissions shared.Role , project string , asset string ) error {
323+ concurrencyMutex .Lock ()
324+ defer concurrencyMutex .Unlock ()
280325 _ , err := c .enforcer .AddRoleForUserInDomainCtx (ctx , c .getProjectRoleName (projectRoleWhichGetsPermission , project ), c .getAssetRoleName (assetRoleWhichProvidesPermissions , asset ), "domain::" + c .domain )
281326 return err
282327}
@@ -286,6 +331,8 @@ func (c *casbinRBAC) AllowRole(ctx context.Context, role shared.Role, object sha
286331 for i , ac := range action {
287332 policies [i ] = []string {"role::" + string (role ), "domain::" + c .domain , "obj::" + string (object ), "act::" + string (ac )}
288333 }
334+ concurrencyMutex .Lock ()
335+ defer concurrencyMutex .Unlock ()
289336 _ , err := c .enforcer .AddPoliciesCtx (ctx , policies )
290337 return err
291338}
@@ -295,6 +342,8 @@ func (c *casbinRBAC) AllowRoleInProject(ctx context.Context, project string, rol
295342 for i , ac := range action {
296343 policies [i ] = []string {"project::" + project + "|role::" + string (role ), "domain::" + c .domain , "project::" + project + "|obj::" + string (object ), "act::" + string (ac )}
297344 }
345+ concurrencyMutex .Lock ()
346+ defer concurrencyMutex .Unlock ()
298347 _ , err := c .enforcer .AddPoliciesCtx (ctx , policies )
299348 return err
300349}
@@ -304,12 +353,16 @@ func (c *casbinRBAC) AllowRoleInAsset(ctx context.Context, asset string, role sh
304353 for i , ac := range action {
305354 policies [i ] = []string {"asset::" + asset + "|role::" + string (role ), "domain::" + c .domain , "asset::" + asset + "|obj::" + string (object ), "act::" + string (ac )}
306355 }
356+ concurrencyMutex .Lock ()
357+ defer concurrencyMutex .Unlock ()
307358 _ , err := c .enforcer .AddPoliciesCtx (ctx , policies )
308359 return err
309360}
310361
311362func (c * casbinRBAC ) IsAllowed (ctx context.Context , user string , object shared.Object , action shared.Action ) (bool , error ) {
363+ concurrencyMutex .RLock ()
312364 permissions , err := c .enforcer .GetImplicitPermissionsForUser ("user::" + user , "domain::" + c .domain )
365+ concurrencyMutex .RUnlock ()
313366 if err != nil {
314367 return false , err
315368 }
@@ -322,7 +375,9 @@ func (c *casbinRBAC) IsAllowed(ctx context.Context, user string, object shared.O
322375}
323376
324377func (c * casbinRBAC ) IsAllowedInProject (ctx context.Context , project * models.Project , user string , object shared.Object , action shared.Action ) (bool , error ) {
378+ concurrencyMutex .RLock ()
325379 permissions , err := c .enforcer .GetImplicitPermissionsForUser ("user::" + user , "domain::" + c .domain )
380+ concurrencyMutex .RUnlock ()
326381 if err != nil {
327382 return false , err
328383 }
@@ -336,7 +391,9 @@ func (c *casbinRBAC) IsAllowedInProject(ctx context.Context, project *models.Pro
336391}
337392
338393func (c * casbinRBAC ) IsAllowedInAsset (ctx context.Context , asset * models.Asset , user string , object shared.Object , action shared.Action ) (bool , error ) {
394+ concurrencyMutex .RLock ()
339395 permissions , err := c .enforcer .GetImplicitPermissionsForUser ("user::" + user , "domain::" + c .domain )
396+ concurrencyMutex .RUnlock ()
340397 if err != nil {
341398 return false , err
342399 }
@@ -350,7 +407,9 @@ func (c *casbinRBAC) IsAllowedInAsset(ctx context.Context, asset *models.Asset,
350407}
351408
352409func (c casbinRBACProvider ) DomainsOfUser (user string ) ([]string , error ) {
410+ concurrencyMutex .RLock ()
353411 domains , err := c .enforcer .GetDomainsForUser ("user::" + user )
412+ concurrencyMutex .RUnlock ()
354413 if err != nil {
355414 return nil , err
356415 }
@@ -396,6 +455,8 @@ func buildEnforcer(db *gorm.DB, broker shared.PubSubBroker) (*casbin.ContextEnfo
396455 return nil , fmt .Errorf ("could not set watcher: %w" , err )
397456 }
398457 err = watcher .SetUpdateCallback (func (string ) {
458+ concurrencyMutex .Lock ()
459+ defer concurrencyMutex .Unlock ()
399460 err := e .LoadPolicy ()
400461 if err != nil {
401462 slog .Error ("error while loading policy after update" , "err" , err )
0 commit comments