@@ -3,52 +3,98 @@ package rbac
33import (
44 "context"
55 "strings"
6+
7+ "github.com/google/uuid"
68)
79
810type 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
2150func (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
3574func (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
3981func (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
85131func (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