Skip to content

Commit 9c9c55a

Browse files
authored
fix(authZ): extract enforcer logic (#2553)
Signed-off-by: Miguel Martinez <[email protected]> Signed-off-by: Miguel Martinez Trivino <[email protected]>
1 parent 92f96b6 commit 9c9c55a

File tree

23 files changed

+556
-267
lines changed

23 files changed

+556
-267
lines changed

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,5 @@ Code reviews are required for all submissions via GitHub pull requests.
269269
- when creating PR message, keep it high-level, what functionality was added, don't add info about testing, no icons, no info about how the message was generated.
270270
- app/controlplane/api/gen/frontend/google/protobuf/descriptor.ts is a special case that we don't want to upgrade, so if it upgrades, put it back to main
271271
- when creating a commit or PR message, NEVER add co-authored by or generated by Claude code
272-
- if you modify a schema, remember to run `make migration_sync`
272+
- any call to authorization Enforce done from the biz or svc layer must be done using biz.AuthzUseCase
273+
- if you modify a schema, remember to run `make migration_sync`

app/controlplane/cmd/wire.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func wireApp(*conf.Bootstrap, credentials.ReaderWriter, log.Logger, sdk.Availabl
5353
wire.FieldsOf(new(*conf.Bootstrap), "Server", "Auth", "Data", "CasServer", "ReferrerSharedIndex", "Onboarding", "PrometheusIntegration", "PolicyProviders", "NatsServer", "FederatedAuthentication"),
5454
wire.FieldsOf(new(*conf.Data), "Database"),
5555
dispatcher.New,
56-
authz.NewInMemoryEnforcer,
56+
authz.NewCasbinEnforcer,
5757
policies.NewRegistry,
5858
newApp,
5959
newProtoValidator,
@@ -65,13 +65,23 @@ func wireApp(*conf.Bootstrap, credentials.ReaderWriter, log.Logger, sdk.Availabl
6565
newAuthAllowList,
6666
newJWTConfig,
6767
authzConfig,
68+
authzUseCaseConfig,
6869
biz.NewIndexConfig,
6970
),
7071
)
7172
}
7273

73-
func authzConfig(conf *conf.Bootstrap) *authz.Config {
74-
return &authz.Config{RolesMap: authz.RolesMap, RestrictOrgCreation: conf.RestrictOrgCreation}
74+
func authzConfig() *authz.Config {
75+
return &authz.Config{RolesMap: authz.RolesMap}
76+
}
77+
78+
func authzUseCaseConfig(conf *conf.Bootstrap, casbinEnforcer *authz.CasbinEnforcer, apiTokenRepo biz.APITokenRepo, logger log.Logger) *biz.AuthzUseCaseConfig {
79+
return &biz.AuthzUseCaseConfig{
80+
CasbinEnforcer: casbinEnforcer,
81+
APITokenRepo: apiTokenRepo,
82+
RestrictOrgCreation: conf.RestrictOrgCreation,
83+
Logger: logger,
84+
}
7585
}
7686

7787
func newJWTConfig(conf *conf.Auth) *biz.APITokenJWTConfig {
@@ -96,10 +106,10 @@ func newPolicyProviderConfig(in []*conf.PolicyProvider) []*policies.NewRegistryC
96106
return out
97107
}
98108

99-
func serviceOpts(l log.Logger, enforcer *authz.Enforcer, pUC *biz.ProjectUseCase, gUC *biz.GroupUseCase) []service.NewOpt {
109+
func serviceOpts(l log.Logger, authzUC *biz.AuthzUseCase, pUC *biz.ProjectUseCase, gUC *biz.GroupUseCase) []service.NewOpt {
100110
return []service.NewOpt{
101111
service.WithLogger(l),
102-
service.WithEnforcer(enforcer),
112+
service.WithEnforcer(authzUC),
103113
service.WithProjectUseCase(pUC),
104114
service.WithGroupUseCase(gUC),
105115
}

app/controlplane/cmd/wire_gen.go

Lines changed: 27 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/configs/config.devel.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ prometheus_integration:
9898
- org_name: "development"
9999

100100
# Policy providers configuration
101-
# policy_providers:
102-
# - name: chainloop
103-
# default: true
104-
# url: http://localhost:8002/v1
101+
policy_providers:
102+
- name: chainloop
103+
default: true
104+
url: http://localhost:8002/v1
105105

106106
enable_profiler: true
107107
# federated_authentication:

app/controlplane/internal/server/grpc.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
conf "github.com/chainloop-dev/chainloop/app/controlplane/internal/conf/controlplane/config/v1"
2626
"github.com/chainloop-dev/chainloop/app/controlplane/internal/sentrycontext"
2727
"github.com/chainloop-dev/chainloop/app/controlplane/internal/usercontext/attjwtmiddleware"
28-
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/authz"
2928
authzMiddleware "github.com/chainloop-dev/chainloop/app/controlplane/pkg/authz/middleware"
3029
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/biz"
3130
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/jwt/user"
@@ -51,6 +50,7 @@ import (
5150

5251
type Opts struct {
5352
// UseCases
53+
AuthzUseCase *biz.AuthzUseCase
5454
UserUseCase *biz.UserUseCase
5555
RobotAccountUseCase *biz.RobotAccountUseCase
5656
CASBackendUseCase *biz.CASBackendUseCase
@@ -91,7 +91,6 @@ type Opts struct {
9191
FederatedConfig *conf.FederatedAuthentication
9292
BootstrapConfig *conf.Bootstrap
9393
Credentials credentials.ReaderWriter
94-
Enforcer *authz.Enforcer
9594
Validator *protovalidate.Validator
9695
}
9796

@@ -198,7 +197,7 @@ func craftMiddleware(opts *Opts) []middleware.Middleware {
198197
// 2.d- Set its organization
199198
usercontext.WithCurrentOrganizationMiddleware(opts.UserUseCase, logHelper),
200199
// 3 - Check user/token authorization
201-
authzMiddleware.WithAuthzMiddleware(opts.Enforcer, logHelper),
200+
authzMiddleware.WithAuthzMiddleware(opts.AuthzUseCase, logHelper),
202201
).Match(requireAllButOrganizationOperationsMatcher()).Build(),
203202
// Store all memberships in the context
204203
usercontext.WithCurrentMembershipsMiddleware(opts.MembershipUseCase),

app/controlplane/internal/service/cascredential.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2023 The Chainloop Authors.
2+
// Copyright 2023-2025 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -34,18 +34,18 @@ type CASCredentialsService struct {
3434
casUC *biz.CASCredentialsUseCase
3535
casBackendUC *biz.CASBackendUseCase
3636
casMappingUC *biz.CASMappingUseCase
37-
authz *authz.Enforcer
37+
authzUC *biz.AuthzUseCase
3838
}
3939

40-
func NewCASCredentialsService(casUC *biz.CASCredentialsUseCase, casmUC *biz.CASMappingUseCase, casBUC *biz.CASBackendUseCase, authz *authz.Enforcer, opts ...NewOpt) *CASCredentialsService {
40+
func NewCASCredentialsService(casUC *biz.CASCredentialsUseCase, casmUC *biz.CASMappingUseCase, casBUC *biz.CASBackendUseCase, authzUC *biz.AuthzUseCase, opts ...NewOpt) *CASCredentialsService {
4141
return &CASCredentialsService{
4242
service: newService(opts...),
4343
casUC: casUC,
4444
// we use the casMappingUC to find the backend to download from
4545
casMappingUC: casmUC,
4646
// we use the casBackendUC to find the default upload backend
4747
casBackendUC: casBUC,
48-
authz: authz,
48+
authzUC: authzUC,
4949
}
5050
}
5151

@@ -79,7 +79,7 @@ func (s *CASCredentialsService) Get(ctx context.Context, req *pb.CASCredentialsS
7979
}
8080

8181
// Enforce required role
82-
if ok, err := s.authz.Enforce(currentAuthzSubject, policyToCheck); err != nil {
82+
if ok, err := s.authzUC.Enforce(ctx, currentAuthzSubject, policyToCheck); err != nil {
8383
return nil, handleUseCaseErr(err, s.log)
8484
} else if !ok {
8585
return nil, errors.Forbidden("forbidden", "not allowed to perform this operation")

app/controlplane/internal/service/group.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ func (g *GroupService) userHasPermissionOnGroupMembershipsWithPolicy(ctx context
620620
m := entities.CurrentMembership(ctx)
621621
for _, rm := range m.Resources {
622622
if rm.ResourceType == authz.ResourceTypeGroup && rm.ResourceID == resolvedGroupID {
623-
pass, err := g.enforcer.Enforce(string(rm.Role), policy)
623+
pass, err := g.authz.Enforce(ctx, string(rm.Role), policy)
624624
if err != nil {
625625
return handleUseCaseErr(err, g.log)
626626
}

app/controlplane/internal/service/organization.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ func (s *OrganizationService) UpdateMembership(ctx context.Context, req *pb.Orga
212212

213213
func (s *OrganizationService) canCreateOrganization(ctx context.Context) (bool, error) {
214214
// Restricted org creation is disabled, allow creation
215-
if !s.enforcer.RestrictOrgCreation {
215+
if !s.authz.RestrictOrgCreation {
216216
return true, nil
217217
}
218218

@@ -222,7 +222,7 @@ func (s *OrganizationService) canCreateOrganization(ctx context.Context) (bool,
222222
continue
223223
}
224224

225-
pass, err := s.enforcer.Enforce(string(rm.Role), authz.PolicyOrganizationCreate)
225+
pass, err := s.authz.Enforce(ctx, string(rm.Role), authz.PolicyOrganizationCreate)
226226
if err != nil {
227227
return false, handleUseCaseErr(err, s.log)
228228
}

app/controlplane/internal/service/service.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func newService(opts ...NewOpt) *service {
133133

134134
type service struct {
135135
log *log.Helper
136-
enforcer *authz.Enforcer
136+
authz *biz.AuthzUseCase
137137
projectUseCase *biz.ProjectUseCase
138138
groupUseCase *biz.GroupUseCase
139139
}
@@ -146,9 +146,9 @@ func WithLogger(logger log.Logger) NewOpt {
146146
}
147147
}
148148

149-
func WithEnforcer(enforcer *authz.Enforcer) NewOpt {
149+
func WithEnforcer(authzUC *biz.AuthzUseCase) NewOpt {
150150
return func(s *service) {
151-
s.enforcer = enforcer
151+
s.authz = authzUC
152152
}
153153
}
154154

@@ -231,7 +231,7 @@ func (s *service) authorizeResource(ctx context.Context, op *authz.Policy, resou
231231
// Try to enforce the policy with each matching role
232232
// If any role passes, authorize the request
233233
for _, rm := range matchingResources {
234-
pass, err := s.enforcer.Enforce(string(rm.Role), op)
234+
pass, err := s.authz.Enforce(ctx, string(rm.Role), op)
235235
if err != nil {
236236
return handleUseCaseErr(err, s.log)
237237
}
@@ -289,7 +289,7 @@ func (s *service) userCanCreateProject(ctx context.Context) error {
289289
}
290290

291291
orgRole := usercontext.CurrentAuthzSubject(ctx)
292-
pass, err := s.enforcer.Enforce(orgRole, authz.PolicyProjectCreate)
292+
pass, err := s.authz.Enforce(ctx, orgRole, authz.PolicyProjectCreate)
293293
if err != nil {
294294
return handleUseCaseErr(err, s.log)
295295
}

app/controlplane/pkg/authz/authz_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func TestDoSync(t *testing.T) {
7676
}
7777

7878
// load custom policies
79-
err := doSync(e, &Config{RolesMap: policiesM})
79+
err := syncRBACRoles(e, &Config{RolesMap: policiesM})
8080
assert.NoError(t, err)
8181
got, err := e.GetPolicy()
8282
assert.NoError(t, err)
@@ -92,7 +92,7 @@ func TestDoSync(t *testing.T) {
9292
},
9393
}
9494

95-
err = doSync(e, &Config{RolesMap: policiesM})
95+
err = syncRBACRoles(e, &Config{RolesMap: policiesM})
9696
assert.NoError(t, err)
9797
got, err = e.GetPolicy()
9898
assert.NoError(t, err)
@@ -105,7 +105,7 @@ func TestDoSync(t *testing.T) {
105105
},
106106
}
107107

108-
err = doSync(e, &Config{RolesMap: policiesM})
108+
err = syncRBACRoles(e, &Config{RolesMap: policiesM})
109109
assert.NoError(t, err)
110110
got, err = e.GetPolicy()
111111
assert.NoError(t, err)
@@ -117,7 +117,7 @@ func TestDoSync(t *testing.T) {
117117
PolicyAttachedIntegrationDetach,
118118
},
119119
}
120-
err = doSync(e, &Config{RolesMap: policiesM})
120+
err = syncRBACRoles(e, &Config{RolesMap: policiesM})
121121
assert.NoError(t, err)
122122
got, err = e.GetPolicy()
123123
assert.NoError(t, err)
@@ -129,13 +129,13 @@ func TestDoSync(t *testing.T) {
129129
assert.Equal(t, "delete", got[0][2])
130130
}
131131

132-
func testEnforcer(t *testing.T) (*Enforcer, io.Closer) {
132+
func testEnforcer(t *testing.T) (*CasbinEnforcer, io.Closer) {
133133
f, err := os.CreateTemp(t.TempDir(), "policy*.csv")
134134
if err != nil {
135135
require.FailNow(t, err.Error())
136136
}
137137

138-
enforcer, err := NewFiletypeEnforcer(f.Name(), &Config{})
138+
enforcer, err := NewCasbinEnforcer(&Config{})
139139
require.NoError(t, err)
140140
return enforcer, f
141141
}

0 commit comments

Comments
 (0)