1- component singleton {
1+ component singleton accessors = " true " {
22
33 property name = " settings" inject = " coldbox:moduleSettings:cbguard" ;
44 property name = " moduleService" inject = " coldbox:moduleService" ;
55 property name = " wirebox" inject = " wirebox" ;
66
7- function allows ( required any permissions , struct additionalArgs = {}, boolean negate = false ) {
8- var context = preflight ();
7+ property name = " guards" type = " struct" ;
98
10- for ( var permission in arrayWrap ( arguments .permissions ) ) {
11- var hasPermission = invoke (
12- context .user ,
13- context .props .methodNames [ " hasPermission" ],
14- {
15- " permission" : permission ,
16- " additionalArgs" : arguments .additionalArgs
17- }
9+ /**
10+ * Creates a new Guard service
11+ *
12+ * @return s cbguard.models.Guard
13+ */
14+ public Guard function init () {
15+ variables .guards = {};
16+ return this ;
17+ }
18+
19+ /**
20+ * Defines a new custom guard. A custom guard is used instead of the
21+ * user model's `hasPermission` method.
22+ *
23+ * @name The name of the permission to match for this custom guard.
24+ * @callback The callback to resolve the custom guard. It should return a boolean.
25+ * This can be one of the following:
26+ * 1. A UDF or closure.
27+ * 2. A component with an `authorize` method.
28+ * 3. A WireBox mapping that resolves to a component with an `authorize` method.
29+ *
30+ * @throws InvalidGuardType
31+ *
32+ * @return s cbguard.models.Guard
33+ */
34+ public Guard function define ( required string name , required any callback ) {
35+ if ( isSimpleValue ( arguments .callback ) ) {
36+ arguments .callback = variables .wirebox .getInstance ( dsl = callback );
37+ if ( ! structKeyExists ( arguments .callback , " authorize" ) ) {
38+ throw (
39+ type = " InvalidGuardType" ,
40+ message = " A component guard must have an `authorize` method defined."
41+ );
42+ }
43+ } else if ( isClosure ( arguments .callback ) || isCustomFunction ( arguments .callback ) ) {
44+ arguments .callback = { " authorize" : arguments .callback };
45+ } else {
46+ throw (
47+ type = " InvalidGuardType" ,
48+ message = " Cannot define a guard without either a component with an `authorize` method, a WireBox mapping to a component with an `authorize` method, or a closure or UDF function."
1849 );
50+ }
51+
52+ variables .guards [ arguments .name ] = arguments .callback ;
53+ return this ;
54+ }
1955
20- if ( hasPermission ) {
56+ /**
57+ * Removes a custom guard definition.
58+ *
59+ * @name The name of the permission to remove the custom guard.
60+ * @return s cbguard.models.Guard
61+ */
62+ public Guard function removeDefinition ( required string name ) {
63+ structDelete ( variables .guards , arguments .name );
64+ return this ;
65+ }
66+
67+
68+ /**
69+ * Returns true if the logged in user is allowed for any of the permissions.
70+ *
71+ * @permissions A single string permission, list of string permissions,
72+ * or array of string permissions to check.
73+ * @additionalArgs A struct of any additional arguments to pass to the guard.
74+ *
75+ * @return s boolean
76+ */
77+ public boolean function allows ( required any permissions , struct additionalArgs = {} ) {
78+ var context = preflight ();
79+
80+ for ( var permission in arrayWrap ( arguments .permissions ) ) {
81+ if ( resolvePermission ( permission , context , arguments .additionalArgs ) ) {
2182 return true ;
2283 }
2384 }
2485
2586 return false ;
2687 }
2788
28- function denies ( required any permissions , struct additionalArgs = {} ) {
89+ /**
90+ * Returns true if the logged in user is not allowed for at least one of the permissions.
91+ *
92+ * @permissions A single string permission, list of string permissions,
93+ * or array of string permissions to check.
94+ * @additionalArgs A struct of any additional arguments to pass to the guard.
95+ *
96+ * @return s boolean
97+ */
98+ public boolean function denies ( required any permissions , struct additionalArgs = {} ) {
2999 var context = preflight ();
30100
31101 for ( var permission in arrayWrap ( arguments .permissions ) ) {
32- var hasPermission = invoke (
33- context .user ,
34- context .props .methodNames [ " hasPermission" ],
35- {
36- " permission" : permission ,
37- " additionalArgs" : arguments .additionalArgs
38- }
39- );
40-
41- if ( ! hasPermission ) {
102+ if ( ! resolvePermission ( permission , context , arguments .additionalArgs ) ) {
42103 return true ;
43104 }
44105 }
45106
46107 return false ;
47108 }
48109
110+ /**
111+ * Returns true if the logged in user is allowed for all of the permissions.
112+ *
113+ * @permissions A single string permission, list of string permissions,
114+ * or array of string permissions to check.
115+ * @additionalArgs A struct of any additional arguments to pass to the guard.
116+ *
117+ * @return s boolean
118+ */
49119 public boolean function all ( required any permissions , struct additionalArgs = {} ) {
50120 var context = preflight ();
51121
52122 for ( var permission in arrayWrap ( arguments .permissions ) ) {
53- var hasPermission = invoke (
54- context .user ,
55- context .props .methodNames [ " hasPermission" ],
56- {
57- " permission" : permission ,
58- " additionalArgs" : arguments .additionalArgs
59- }
60- );
61-
62- if ( ! hasPermission ) {
123+ if ( ! resolvePermission ( permission , context , arguments .additionalArgs ) ) {
63124 return false ;
64125 }
65126 }
66127
67128 return true ;
68129 }
69130
131+ /**
132+ * Returns true if the logged in user is denied for all of the permissions.
133+ *
134+ * @permissions A single string permission, list of string permissions,
135+ * or array of string permissions to check.
136+ * @additionalArgs A struct of any additional arguments to pass to the guard.
137+ *
138+ * @return s boolean
139+ */
70140 public boolean function none ( required any permissions , struct additionalArgs = {} ) {
71141 var context = preflight ();
72142
73143 for ( var permission in arrayWrap ( arguments .permissions ) ) {
74- var hasPermission = invoke (
75- context .user ,
76- context .props .methodNames [ " hasPermission" ],
77- {
78- " permission" : permission ,
79- " additionalArgs" : arguments .additionalArgs
80- }
81- );
82-
83- if ( hasPermission ) {
144+ if ( resolvePermission ( permission , context , arguments .additionalArgs ) ) {
84145 return false ;
85146 }
86147 }
87148
88149 return true ;
89150 }
90151
91- public void function authorize (
152+ /**
153+ * Throws an exception if the logged in user is not allowed for any of the permissions.
154+ *
155+ * @permissions A single string permission, list of string permissions,
156+ * or array of string permissions to check.
157+ * @additionalArgs A struct of any additional arguments to pass to the guard.
158+ * @errorMessage The error message to throw with the exception.
159+ * It can be either:
160+ * 1. A string error message
161+ * 2. A closure or UDF that will produce a string error
162+ * message. This callback receives the following arguments:
163+ * a. The `permissions` tried.
164+ * b. The logged in `user`.
165+ * c. The `additionalArgs` passed.
166+ *
167+ * @throws NotAuthorized
168+ *
169+ * @return s cbguard.models.Guard
170+ */
171+ public Guard function authorize (
92172 required any permissions ,
93173 struct additionalArgs = {},
94- string errorMessage
174+ any errorMessage
95175 ) {
96176 var context = preflight ();
97177
98- var failedPermission = " " ;
99- for ( var permission in arrayWrap ( arguments .permissions ) ) {
100- var hasPermission = invoke (
101- context .user ,
102- context .props .methodNames [ " hasPermission" ],
103- {
104- " permission" : permission ,
105- " additionalArgs" : arguments .additionalArgs
106- }
107- );
108-
109- if ( ! hasPermission ) {
110- failedPermission = permission ;
178+ arguments .permissions = arrayWrap ( arguments .permissions );
179+ var passed = false ;
180+ for ( var permission in arguments .permissions ) {
181+ if ( resolvePermission ( permission , context , arguments .additionalArgs ) ) {
182+ passed = true ;
111183 break ;
112184 }
113185 }
114186
115- if ( failedPermission ! = " " ) {
187+ if ( ! passed ) {
116188 param arguments .errorMessage = " The logged in user is not authorized to access this resource" ;
117189
118190 if ( isClosure ( arguments .errorMessage ) || isCustomFunction ( arguments .errorMessage ) ) {
119191 arguments .errorMessage = arguments .errorMessage (
120- failedPermission = failedPermission ,
192+ permissions = arguments . permissions ,
121193 user = context .user ,
122194 additionalArgs = arguments .additionalArgs
123195 );
@@ -128,8 +200,19 @@ component singleton {
128200 message = arguments .errorMessage
129201 );
130202 }
203+
204+ return this ;
131205 }
132206
207+ /**
208+ * Handles getting the current cbguard context for this guard.
209+ * Returns a struct with the current `RequestContext` (`event`), the current
210+ * settings for the request (`props`), and the currently logged in user (`user`).
211+ *
212+ * @throws NotLoggedIn
213+ *
214+ * @return s { "event", "props", "user" }
215+ */
133216 private struct function preflight () {
134217 var event = variables .wirebox .getInstance ( dsl = " coldbox:requestContext" );
135218
@@ -161,6 +244,49 @@ component singleton {
161244 };
162245 }
163246
247+ /**
248+ * Resolves the permission check. It first checks and tries any custom guards.
249+ * If no custom guards exist, it checks the current user's `hasPermission` method.
250+ *
251+ * @permission The permission being checked.
252+ * @context The guard context, including `event`, `props`, and `user`.
253+ * @additionalArgs A struct of any additional arguments to pass to the guard.
254+ *
255+ * @return boolean
256+ */
257+ private boolean function resolvePermission (
258+ required string permission ,
259+ required struct context ,
260+ struct additionalArgs = {}
261+ ) {
262+ if ( variables .guards .keyExists ( arguments .permission ) ) {
263+ return invoke (
264+ variables .guards [ arguments .permission ],
265+ " authorize" ,
266+ {
267+ " user" : arguments .context .user ,
268+ " additionalArgs" : arguments .additionalArgs
269+ }
270+ );
271+ }
272+
273+ return invoke (
274+ arguments .context .user ,
275+ arguments .context .props .methodNames [ " hasPermission" ],
276+ {
277+ " permission" : arguments .permission ,
278+ " additionalArgs" : arguments .additionalArgs
279+ }
280+ );
281+ }
282+
283+ /**
284+ * Ensures that the returned value is an array.
285+ * Returns a passed array unmodified. Calls `listToArray` on all other values.
286+ *
287+ * @doc_generic any
288+ * @return [any]
289+ */
164290 private array function arrayWrap ( required any items ) {
165291 return isArray ( arguments .items ) ? items : items .listToArray ();
166292 }
0 commit comments