Skip to content

Commit 859a4a8

Browse files
committed
refactor: enhance RBAC with validation and error handling for role and permission creation
1 parent a3b0ebb commit 859a4a8

4 files changed

Lines changed: 121 additions & 36 deletions

File tree

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module github.com/codescalers/rbac
22

33
go 1.24.6
4+
5+
require github.com/google/uuid v1.6.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

pkg/errors.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package rbac
22

33
var (
4-
ErrNotFound = errorString("not found")
5-
ErrAlreadyExists = errorString("already exists")
4+
ErrAlreadyExists = errorString("already exists")
5+
ErrInvalidResourceOrAction = errorString("invalid resource or action")
6+
ErrInvalidName = errorString("invalid name")
67
)
78

89
type errorString string

pkg/rbac.go

Lines changed: 114 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,98 @@ package rbac
33
import (
44
"context"
55
"strings"
6+
7+
"github.com/google/uuid"
68
)
79

810
type RBAC struct {
911
store Store
1012
}
1113

12-
func New(s Store) *RBAC {
13-
return &RBAC{store: s}
14+
func New(ctx context.Context, s Store, opts ...Option) (*RBAC, error) {
15+
r := &RBAC{store: s}
16+
for _, opt := range opts {
17+
if err := opt(ctx, r); err != nil {
18+
return nil, err
19+
}
20+
}
21+
return r, nil
22+
}
23+
24+
type Option func(ctx context.Context, r *RBAC) error
25+
26+
func WithSeed(roles []Role) Option {
27+
return func(ctx context.Context, r *RBAC) error {
28+
return r.initFromSeed(ctx, roles)
29+
}
30+
}
31+
32+
func (r *RBAC) initFromSeed(ctx context.Context, roles []Role) error {
33+
for _, role := range roles {
34+
if err := r.store.CreateRole(ctx, role); err != nil {
35+
return err
36+
}
37+
}
38+
return nil
1439
}
1540

16-
func (r *RBAC) CreateRole(ctx context.Context, role Role) error {
17-
role.Name = strings.ToLower(role.Name)
41+
func (r *RBAC) CreateRole(ctx context.Context, name, description string) error {
42+
n := strings.ToLower(strings.TrimSpace(name))
43+
if n == "" {
44+
return ErrInvalidName
45+
}
46+
role := Role{ID: uuid.New().String(), Name: n, Description: description}
1847
return r.store.CreateRole(ctx, role)
1948
}
2049

2150
func (r *RBAC) RemoveRole(ctx context.Context, roleID string) error {
51+
if err := validateUUIDs(roleID); err != nil {
52+
return err
53+
}
2254
return r.store.RemoveRole(ctx, roleID)
2355
}
2456

25-
func (r *RBAC) CreatePermission(ctx context.Context, p Permission) error {
26-
p.Resource = strings.ToLower(p.Resource)
27-
p.Action = strings.ToLower(p.Action)
57+
func (r *RBAC) CreatePermission(ctx context.Context, resource, action string) error {
58+
res := strings.ToLower(strings.TrimSpace(resource))
59+
a := strings.ToLower(strings.TrimSpace(action))
60+
if res == "" || a == "" {
61+
return ErrInvalidResourceOrAction
62+
}
63+
p := Permission{ID: uuid.New().String(), Resource: res, Action: a}
2864
return r.store.CreatePermission(ctx, p)
2965
}
3066

31-
func (r *RBAC) RemovePermission(ctx context.Context, id string) error {
32-
return r.store.RemovePermission(ctx, id)
67+
func (r *RBAC) RemovePermission(ctx context.Context, permID string) error {
68+
if err := validateUUIDs(permID); err != nil {
69+
return err
70+
}
71+
return r.store.RemovePermission(ctx, permID)
3372
}
3473

3574
func (r *RBAC) AssignRole(ctx context.Context, subjectID, roleID string) error {
75+
if err := validateUUIDs(roleID); err != nil {
76+
return err
77+
}
3678
return r.store.AssignRole(ctx, subjectID, roleID)
3779
}
3880

3981
func (r *RBAC) RevokeRole(ctx context.Context, subjectID, roleID string) error {
82+
if err := validateUUIDs(roleID); err != nil {
83+
return err
84+
}
4085
return r.store.RevokeRole(ctx, subjectID, roleID)
4186
}
4287

43-
func (r *RBAC) AddPermissionToRole(ctx context.Context, roleID string, permID string) error {
88+
func (r *RBAC) AddPermissionToRole(ctx context.Context, roleID, permID string) error {
89+
if err := validateUUIDs(roleID, permID); err != nil {
90+
return err
91+
}
4492
role, err := r.store.GetRole(ctx, roleID)
4593
if err != nil {
4694
return err
4795
}
48-
for _, p := range role.Permissions {
49-
if p.ID == permID {
50-
return ErrAlreadyExists
51-
}
96+
if r.roleHasPermission(&role, permID) {
97+
return ErrAlreadyExists
5298
}
5399
p, err := r.store.GetPermission(ctx, permID)
54100
if err != nil {
@@ -58,31 +104,34 @@ func (r *RBAC) AddPermissionToRole(ctx context.Context, roleID string, permID st
58104
return r.store.UpdateRole(ctx, role)
59105
}
60106

61-
func (r *RBAC) RemovePermissionFromRole(ctx context.Context, roleID string, permID string) error {
107+
func (r *RBAC) RemovePermissionFromRole(ctx context.Context, roleID, permID string) error {
108+
if err := validateUUIDs(roleID, permID); err != nil {
109+
return err
110+
}
62111
role, err := r.store.GetRole(ctx, roleID)
63112
if err != nil {
64113
return err
65114
}
66-
filtered := role.Permissions[:0]
67-
for _, p := range role.Permissions {
68-
if p.ID != permID {
69-
filtered = append(filtered, p)
70-
}
71-
72-
}
73-
role.Permissions = filtered
115+
role.Permissions = r.filterOutPermission(role.Permissions, permID)
74116
return r.store.UpdateRole(ctx, role)
75117
}
76118

77119
// Direct grants
78-
func (r *RBAC) GrantSubject(ctx context.Context, subjectID string, grant Grant) error {
79-
80-
grant.Resource = strings.ToLower(grant.Resource)
81-
grant.Action = strings.ToLower(grant.Action)
120+
func (r *RBAC) GrantSubject(ctx context.Context, subjectID string, resource, action, resourceID string) error {
121+
res := strings.ToLower(strings.TrimSpace(resource))
122+
act := strings.ToLower(strings.TrimSpace(action))
123+
if res == "" || act == "" {
124+
return ErrInvalidResourceOrAction
125+
}
126+
rid := strings.ToLower(strings.TrimSpace(resourceID))
127+
grant := Grant{ID: uuid.New().String(), Resource: res, Action: act, ResourceID: rid}
82128
return r.store.GrantSubject(ctx, subjectID, grant)
83129
}
84130

85131
func (r *RBAC) RevokeSubjectGrant(ctx context.Context, subjectID, grantID string) error {
132+
if err := validateUUIDs(grantID); err != nil {
133+
return err
134+
}
86135
return r.store.RevokeSubjectGrant(ctx, subjectID, grantID)
87136
}
88137

@@ -97,18 +146,21 @@ func (r *RBAC) Can(ctx context.Context, subjectID, action, resource string, reso
97146
}
98147

99148
id := ""
100-
if len(resourceID) > 0 {
149+
if len(resourceID) > 0 && resourceID[0] != "" {
101150
id = resourceID[0]
102151
}
103-
res := strings.ToLower(resource)
104-
act := strings.ToLower(action)
152+
res := strings.ToLower(strings.TrimSpace(resource))
153+
act := strings.ToLower(strings.TrimSpace(action))
154+
if res == "" || act == "" {
155+
return false, ErrInvalidResourceOrAction
156+
}
105157

106158
//direct grants
107159
for _, g := range grants {
108-
if strings.ToLower(g.Resource) != res {
160+
if g.Resource != res {
109161
continue
110162
}
111-
if strings.ToLower(g.Action) != act {
163+
if g.Action != act {
112164
continue
113165
}
114166
if g.ResourceID != id && g.ResourceID != "*" {
@@ -120,10 +172,10 @@ func (r *RBAC) Can(ctx context.Context, subjectID, action, resource string, reso
120172
//role permissions
121173
for _, role := range roles {
122174
for _, p := range role.Permissions {
123-
if strings.ToLower(p.Resource) != res {
175+
if p.Resource != res {
124176
continue
125177
}
126-
if strings.ToLower(p.Action) != act {
178+
if p.Action != act {
127179
continue
128180
}
129181
return true, nil
@@ -132,3 +184,31 @@ func (r *RBAC) Can(ctx context.Context, subjectID, action, resource string, reso
132184

133185
return false, nil
134186
}
187+
188+
func (r *RBAC) roleHasPermission(role *Role, permID string) bool {
189+
for _, p := range role.Permissions {
190+
if p.ID == permID {
191+
return true
192+
}
193+
}
194+
return false
195+
}
196+
197+
func (r *RBAC) filterOutPermission(perms []Permission, permID string) []Permission {
198+
filtered := perms[:0]
199+
for _, p := range perms {
200+
if p.ID != permID {
201+
filtered = append(filtered, p)
202+
}
203+
}
204+
return filtered
205+
}
206+
207+
func validateUUIDs(ids ...string) error {
208+
for _, id := range ids {
209+
if _, err := uuid.Parse(id); err != nil {
210+
return err
211+
}
212+
}
213+
return nil
214+
}

0 commit comments

Comments
 (0)