1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 module hunt.shiro.authz.ModularRealmAuthorizer;
20 
21 import hunt.shiro.authz.Authorizer;
22 import hunt.shiro.authz.permission.Permission;
23 import hunt.shiro.authz.permission.PermissionResolver;
24 import hunt.shiro.authz.permission.PermissionResolverAware;
25 import hunt.shiro.authz.permission.RolePermissionResolver;
26 import hunt.shiro.authz.permission.RolePermissionResolverAware;
27 import hunt.shiro.Exceptions;
28 import hunt.shiro.realm.Realm;
29 import hunt.shiro.subject.PrincipalCollection;
30 import hunt.shiro.util.CollectionUtils;
31 
32 import hunt.Exceptions;
33 import hunt.collection;
34 import hunt.logging;
35 
36 import std.conv;
37 import std.string;
38 import core.atomic;
39 import std.range;
40 
41 
42 /**
43  * A <tt>ModularRealmAuthorizer</tt> is an <tt>Authorizer</tt> implementation that consults one or more configured
44  * {@link Realm Realm}s during an authorization operation.
45  *
46  */
47 class ModularRealmAuthorizer : Authorizer, PermissionResolverAware, RolePermissionResolverAware {
48 
49     /**
50      * The realms to consult during any authorization check.
51      */
52     protected Realm[] realms;
53 
54     /**
55      * A PermissionResolver to be used by <em>all</em> configured realms.  Leave <code>null</code> if you wish
56      * to configure different resolvers for different realms.
57      */
58     protected PermissionResolver permissionResolver;
59 
60     /**
61      * A RolePermissionResolver to be used by <em>all</em> configured realms.  Leave <code>null</code> if you wish
62      * to configure different resolvers for different realms.
63      */
64     protected RolePermissionResolver rolePermissionResolver;
65 
66     /**
67      * Default no-argument constructor, does nothing.
68      */
69     this() {
70     }
71 
72     /**
73      * Constructor that accepts the <code>Realm</code>s to consult during an authorization check.  Immediately calls
74      * {@link #setRealms setRealms(realms)}.
75      *
76      * @param realms the realms to consult during an authorization check.
77      */
78     this(Realm[] realms) {
79         setRealms(realms);
80     }
81 
82     /**
83      * Returns the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
84      *
85      * @return the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
86      */
87     Realm[] getRealms() {
88         return this.realms;
89     }
90 
91     /**
92      * Sets the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
93      *
94      * @param realms the realms wrapped by this <code>Authorizer</code> which are consulted during an authorization check.
95      */
96     void setRealms(Realm[] realms) {
97         this.realms = realms;
98         applyPermissionResolverToRealms();
99         applyRolePermissionResolverToRealms();
100     }
101 
102     /**
103      * Returns the PermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
104      * if all realm instances will each configure their own permission resolver.
105      *
106      * @return the PermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
107      *         if realm instances will each configure their own permission resolver.
108      */
109     PermissionResolver getPermissionResolver() {
110         return this.permissionResolver;
111     }
112 
113     /**
114      * Sets the specified {@link PermissionResolver PermissionResolver} on <em>all</em> of the wrapped realms that
115      * implement the {@link hunt.shiro.authz.permission.PermissionResolverAware PermissionResolverAware} interface.
116      * <p/>
117      * Only call this method if you want the permission resolver to be passed to all realms that implement the
118      * <code>PermissionResolver</code> interface.  If you do not want this to occur, the realms must
119      * configure themselves individually (or be configured individually).
120      *
121      * @param permissionResolver the permissionResolver to set on all of the wrapped realms that implement the
122      *                           {@link hunt.shiro.authz.permission.PermissionResolverAware PermissionResolverAware} interface.
123      */
124     void setPermissionResolver(PermissionResolver permissionResolver) {
125         this.permissionResolver = permissionResolver;
126         applyPermissionResolverToRealms();
127     }
128 
129     /**
130      * Sets the internal {@link #getPermissionResolver} on any internal configured
131      * {@link #getRealms Realms} that implement the {@link hunt.shiro.authz.permission.PermissionResolverAware PermissionResolverAware} interface.
132      * <p/>
133      * This method is called after setting a permissionResolver on this ModularRealmAuthorizer via the
134      * {@link #setPermissionResolver(hunt.shiro.authz.permission.PermissionResolver) setPermissionResolver} method.
135      * <p/>
136      * It is also called after setting one or more realms via the {@link #setRealms setRealms} method to allow these
137      * newly available realms to be given the <code>PermissionResolver</code> already in use.
138      *
139      */
140     protected void applyPermissionResolverToRealms() {
141         PermissionResolver resolver = getPermissionResolver();
142         Realm[] realms = getRealms();
143         if (resolver !is null && !realms.empty()) {
144             foreach (Realm realm; realms) {
145                 auto realmCast = cast(PermissionResolverAware) realm;
146                 if (realmCast !is null) {
147                     realmCast.setPermissionResolver(resolver);
148                 }
149             }
150         }
151     }
152 
153     /**
154      * Returns the RolePermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
155      * if all realm instances will each configure their own permission resolver.
156      *
157      * @return the RolePermissionResolver to be used on <em>all</em> configured realms, or <code>null</code (the default)
158      *         if realm instances will each configure their own role permission resolver.
159      */
160     RolePermissionResolver getRolePermissionResolver() {
161         return this.rolePermissionResolver;
162     }
163 
164     /**
165      * Sets the specified {@link RolePermissionResolver RolePermissionResolver} on <em>all</em> of the wrapped realms that
166      * implement the {@link hunt.shiro.authz.permission.RolePermissionResolverAware PermissionResolverAware} interface.
167      * <p/>
168      * Only call this method if you want the permission resolver to be passed to all realms that implement the
169      * <code>RolePermissionResolver</code> interface.  If you do not want this to occur, the realms must
170      * configure themselves individually (or be configured individually).
171      *
172      * @param rolePermissionResolver the rolePermissionResolver to set on all of the wrapped realms that implement the
173      *                               {@link hunt.shiro.authz.permission.RolePermissionResolverAware RolePermissionResolverAware} interface.
174      */
175     void setRolePermissionResolver(RolePermissionResolver rolePermissionResolver) {
176         this.rolePermissionResolver = rolePermissionResolver;
177         applyRolePermissionResolverToRealms();
178     }
179 
180     /**
181      * Sets the internal {@link #getRolePermissionResolver} on any internal configured
182      * {@link #getRealms Realms} that implement the {@link hunt.shiro.authz.permission.RolePermissionResolverAware RolePermissionResolverAware} interface.
183      * <p/>
184      * This method is called after setting a rolePermissionResolver on this ModularRealmAuthorizer via the
185      * {@link #setRolePermissionResolver(hunt.shiro.authz.permission.RolePermissionResolver) setRolePermissionResolver} method.
186      * <p/>
187      * It is also called after setting one or more realms via the {@link #setRealms setRealms} method to allow these
188      * newly available realms to be given the <code>RolePermissionResolver</code> already in use.
189      *
190      */
191     protected void applyRolePermissionResolverToRealms() {
192         RolePermissionResolver resolver = getRolePermissionResolver();
193         Realm[] realms = getRealms();
194         if (resolver !is null && realms.empty()) {
195             foreach (Realm realm; realms) {
196                 auto realmCast = cast(RolePermissionResolverAware) realm;
197                 if (realmCast !is null) {
198                     realmCast.setRolePermissionResolver(resolver);
199                 }
200             }
201         }
202     }
203 
204     /**
205      * Used by the {@link Authorizer Authorizer} implementation methods to ensure that the {@link #setRealms realms}
206      * has been set.  The default implementation ensures the property is not null and not empty.
207      *
208      * @throws IllegalStateException if the <tt>realms</tt> property is configured incorrectly.
209      */
210     protected void assertRealmsConfigured() {
211         Realm[] realms = getRealms();
212         if (realms.empty()) {
213             string msg = "Configuration error:  No realms have been configured!  One or more realms must be "
214                 ~ "present to execute an authorization operation.";
215             throw new IllegalStateException(msg);
216         }
217     }
218 
219     /**
220      * Returns <code>true</code> if any of the configured realms'
221      * {@link #isPermitted(hunt.shiro.subject.PrincipalCollection, string)} returns <code>true</code>,
222      * <code>false</code> otherwise.
223      */
224     bool isPermitted(PrincipalCollection principals, string permission) {
225         assertRealmsConfigured();
226         foreach (Realm realm; getRealms()) {
227             auto realmCast = cast(Authorizer) realm;
228             if (realmCast is null)
229                 continue;
230             if (realmCast.isPermitted(principals, permission)) {
231                 return true;
232             }
233         }
234         return false;
235     }
236 
237     /**
238      * Returns <code>true</code> if any of the configured realms'
239      * {@link #isPermitted(hunt.shiro.subject.PrincipalCollection, Permission)} call returns <code>true</code>,
240      * <code>false</code> otherwise.
241      */
242     bool isPermitted(PrincipalCollection principals, Permission permission) {
243         assertRealmsConfigured();
244         foreach (Realm realm; getRealms()) {
245             auto realmCast = cast(Authorizer) realm;
246             if (realmCast is null)
247                 continue;
248             if (realmCast.isPermitted(principals, permission)) {
249                 return true;
250             }
251         }
252         return false;
253     }
254 
255     /**
256      * Returns <code>true</code> if any of the configured realms'
257      * {@link #isPermittedAll(hunt.shiro.subject.PrincipalCollection, string...)} call returns
258      * <code>true</code>, <code>false</code> otherwise.
259      */
260     bool[] isPermitted(PrincipalCollection principals, string[] permissions...) {
261         assertRealmsConfigured();
262         if (permissions !is null && permissions.length > 0) {
263             bool[] r = new bool[permissions.length];
264             for (int i = 0; i < permissions.length; i++) {
265                 r[i] = isPermitted(principals, permissions[i]);
266             }
267             return r;
268         }
269         return new bool[0];
270     }
271 
272     /**
273      * Returns <code>true</code> if any of the configured realms'
274      * {@link #isPermitted(hunt.shiro.subject.PrincipalCollection, List)} call returns <code>true</code>,
275      * <code>false</code> otherwise.
276      */
277     bool[] isPermitted(PrincipalCollection principals, List!(Permission) permissions) {
278         assertRealmsConfigured();
279         if (permissions !is null && !permissions.isEmpty()) {
280             bool[] r = new bool[permissions.size()];
281             int i = 0;
282             foreach (Permission p; permissions) {
283                 r[i++] = isPermitted(principals, p);
284             }
285             return r;
286         }
287 
288         return new bool[0];
289     }
290 
291     /**
292      * Returns <code>true</code> if any of the configured realms'
293      * {@link #isPermitted(hunt.shiro.subject.PrincipalCollection, string)} call returns <code>true</code>
294      * for <em>all</em> of the specified string permissions, <code>false</code> otherwise.
295      */
296     bool isPermittedAll(PrincipalCollection principals, string[] permissions...) {
297         assertRealmsConfigured();
298         if (permissions !is null && permissions.length > 0) {
299             foreach (string perm; permissions) {
300                 if (!isPermitted(principals, perm)) {
301                     return false;
302                 }
303             }
304         }
305         return true;
306     }
307 
308     /**
309      * Returns <code>true</code> if any of the configured realms'
310      * {@link #isPermitted(hunt.shiro.subject.PrincipalCollection, Permission)} call returns <code>true</code>
311      * for <em>all</em> of the specified Permissions, <code>false</code> otherwise.
312      */
313     bool isPermittedAll(PrincipalCollection principals, Collection!(Permission) permissions) {
314         assertRealmsConfigured();
315         if (permissions !is null && !permissions.isEmpty()) {
316             foreach (Permission permission; permissions) {
317                 if (!isPermitted(principals, permission)) {
318                     return false;
319                 }
320             }
321         }
322         return true;
323     }
324 
325     /**
326      * If !{@link #isPermitted(hunt.shiro.subject.PrincipalCollection, string) isPermitted(permission)}, throws
327      * an <code>UnauthorizedException</code> otherwise returns quietly.
328      */
329     void checkPermission(PrincipalCollection principals, string permission) {
330         assertRealmsConfigured();
331         if (!isPermitted(principals, permission)) {
332             throw new UnauthorizedException("Subject does not have permission [" ~ permission ~ "]");
333         }
334     }
335 
336     /**
337      * If !{@link #isPermitted(hunt.shiro.subject.PrincipalCollection, Permission) isPermitted(permission)}, throws
338      * an <code>UnauthorizedException</code> otherwise returns quietly.
339      */
340     void checkPermission(PrincipalCollection principals, Permission permission) {
341         assertRealmsConfigured();
342         if (!isPermitted(principals, permission)) {
343             throw new UnauthorizedException("Subject does not have permission [" ~ (cast(Object) permission)
344                     .toString() ~ "]");
345         }
346     }
347 
348     /**
349      * If !{@link #isPermitted(hunt.shiro.subject.PrincipalCollection, string...) isPermitted(permission)},
350      *<code>UnauthorizedException</code> otherwise returns quietly.
351      */
352     void checkPermissions(PrincipalCollection principals, string[] permissions...) {
353         assertRealmsConfigured();
354         if (permissions !is null && permissions.length > 0) {
355             foreach (string perm; permissions) {
356                 checkPermission(principals, perm);
357             }
358         }
359     }
360 
361     /**
362      * If !{@link #isPermitted(hunt.shiro.subject.PrincipalCollection, Permission) isPermitted(permission)} for
363      * <em>all</em> the given Permissions, throws
364      * an <code>UnauthorizedException</code> otherwise returns quietly.
365      */
366     void checkPermissions(PrincipalCollection principals, Collection!(Permission) permissions) {
367         assertRealmsConfigured();
368         if (permissions !is null) {
369             foreach (Permission permission; permissions) {
370                 checkPermission(principals, permission);
371             }
372         }
373     }
374 
375     /**
376      * Returns <code>true</code> if any of the configured realms'
377      * {@link #hasRole(hunt.shiro.subject.PrincipalCollection, string)} call returns <code>true</code>,
378      * <code>false</code> otherwise.
379      */
380     bool hasRole(PrincipalCollection principals, string roleIdentifier) {
381         assertRealmsConfigured();
382         foreach (Realm realm; getRealms()) {
383             Authorizer realmCast = cast(Authorizer) realm;
384             if (realmCast is null)
385                 continue;
386             if (realmCast.hasRole(principals, roleIdentifier)) {
387                 return true;
388             }
389         }
390         return false;
391     }
392 
393     /**
394      * Calls {@link #hasRole(hunt.shiro.subject.PrincipalCollection, string)} for each role name in the specified
395      * collection and places the return value from each call at the respective location in the returned array.
396      */
397     bool[] hasRoles(PrincipalCollection principals, List!(string) roleIdentifiers) {
398         if (roleIdentifiers is null || roleIdentifiers.isEmpty()) {
399             assertRealmsConfigured();
400             return new bool[0];
401         }
402 
403         return hasRoles(principals, roleIdentifiers.toArray());
404 
405     }
406 
407     /// ditto
408     bool[] hasRoles(PrincipalCollection principals, string[] roleIdentifiers) {
409         assertRealmsConfigured();
410 
411         if (roleIdentifiers.empty) {
412             return new bool[0];
413         }
414 
415         bool[] hasRoles = new bool[roleIdentifiers.length];
416         int i = 0;
417         foreach (string roleId; roleIdentifiers) {
418             hasRoles[i++] = hasRole(principals, roleId);
419         }
420         return hasRoles;
421     }
422 
423     /**
424      * Returns <code>true</code> iff any of the configured realms'
425      * {@link #hasRole(hunt.shiro.subject.PrincipalCollection, string)} call returns <code>true</code> for
426      * <em>all</em> roles specified, <code>false</code> otherwise.
427      */
428     bool hasAllRoles(PrincipalCollection principals, Collection!(string) roleIdentifiers) {
429         return hasAllRoles(principals, roleIdentifiers.toArray());
430     }
431 
432     bool hasAllRoles(PrincipalCollection principals, string[] roleIdentifiers) {
433         assertRealmsConfigured();
434         foreach (string roleIdentifier; roleIdentifiers) {
435             if (!hasRole(principals, roleIdentifier)) {
436                 return false;
437             }
438         }
439         return true;
440     }
441 
442     /**
443      * If !{@link #hasRole(hunt.shiro.subject.PrincipalCollection, string) hasRole(role)}, throws
444      * an <code>UnauthorizedException</code> otherwise returns quietly.
445      */
446     void checkRole(PrincipalCollection principals, string role) {
447         assertRealmsConfigured();
448         if (!hasRole(principals, role)) {
449             throw new UnauthorizedException("Subject does not have role [" ~ role ~ "]");
450         }
451     }
452 
453     /**
454      * Calls {@link #checkRoles(PrincipalCollection principals, string... roles) checkRoles(PrincipalCollection principals, string... roles) }.
455      */
456     void checkRoles(PrincipalCollection principals, Collection!(string) roles) {
457         //SHIRO-234 - roles.toArray() -> roles.toArray(new string[roles.size()])
458         if (roles !is null && !roles.isEmpty()) {
459             checkRoles(principals, roles.toArray());
460         }
461     }
462 
463     /**
464      * Calls {@link #checkRole(hunt.shiro.subject.PrincipalCollection, string) checkRole} for each role specified.
465      */
466     void checkRoles(PrincipalCollection principals, string[] roles...) {
467         assertRealmsConfigured();
468         if (roles !is null) {
469             foreach (string role; roles) {
470                 checkRole(principals, role);
471             }
472         }
473     }
474 }