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.realm.AuthorizingRealm;
20 
21 import hunt.shiro.realm.AuthenticatingRealm;
22 
23 import hunt.shiro.authc.AuthenticationInfo;
24 import hunt.shiro.authc.AuthenticationToken;
25 import hunt.shiro.authc.credential.CredentialsMatcher;
26 import hunt.shiro.authz;
27 import hunt.shiro.authz.permission;
28 import hunt.shiro.cache.AbstractCacheManager;
29 import hunt.shiro.cache.Cache;
30 import hunt.shiro.cache.CacheManager;
31 import hunt.shiro.Exceptions;
32 import hunt.shiro.subject.PrincipalCollection;
33 import hunt.shiro.util.CollectionUtils;
34 import hunt.shiro.util.Common;
35 
36 import hunt.collection;
37 import hunt.Exceptions;
38 import hunt.logging.Logger;
39 
40 import core.atomic;
41 import std.conv;
42 import std.string;
43 
44 
45 /**
46  * An {@code AuthorizingRealm} extends the {@code AuthenticatingRealm}'s capabilities by adding Authorization
47  * (access control) support.
48  * <p/>
49  * This implementation will perform all role and permission checks automatically (and subclasses do not have to
50  * write this logic) as long as the
51  * {@link #getAuthorizationInfo(hunt.shiro.subject.PrincipalCollection)} method returns an
52  * {@link AuthorizationInfo}.  Please see that method's JavaDoc for an in-depth explanation.
53  * <p/>
54  * If you find that you do not want to utilize the {@link AuthorizationInfo AuthorizationInfo} construct,
55  * you are of course free to subclass the {@link AuthenticatingRealm AuthenticatingRealm} directly instead and
56  * implement the remaining Realm interface methods directly.  You might do this if you want have better control
57  * over how the Role and Permission checks occur for your specific data source.  However, using AuthorizationInfo
58  * (and its default implementation {@link hunt.shiro.authz.SimpleAuthorizationInfo SimpleAuthorizationInfo}) is sufficient in the large
59  * majority of Realm cases.
60  *
61  * @see hunt.shiro.authz.SimpleAuthorizationInfo
62  */
63 abstract class AuthorizingRealm : AuthenticatingRealm,
64         Authorizer, PermissionResolverAware, RolePermissionResolverAware {
65     
66     
67     /*-------------------------------------------
68     |             C O N S T A N T S             |
69     ============================================*/
70 
71 
72     /**
73      * The default suffix appended to the realm name for caching AuthorizationInfo instances.
74      */
75     private enum string DEFAULT_AUTHORIZATION_CACHE_SUFFIX = ".authorizationCache";
76 
77     private static shared int INSTANCE_COUNT = 0; 
78 
79     /*-------------------------------------------
80     |    I N S T A N C E   V A R I A B L E S    |
81     ============================================*/
82     /**
83      * The cache used by this realm to store AuthorizationInfo instances associated with individual Subject principals.
84      */
85     private bool authorizationCachingEnabled;
86     private Cache!(Object, AuthorizationInfo) authorizationCache;
87     private string authorizationCacheName;
88 
89     private PermissionResolver permissionResolver;
90 
91     private RolePermissionResolver permissionRoleResolver;
92 
93     /*-------------------------------------------
94     |         C O N S T R U C T O R S           |
95     ============================================*/
96 
97     this() {
98         this(null, null);
99     }
100 
101     this(CacheManager cacheManager) {
102         this(cacheManager, null);
103     }
104 
105     this(CredentialsMatcher matcher) {
106         this(null, matcher);
107     }
108 
109     this(CacheManager cacheManager, CredentialsMatcher matcher) {
110         super();
111         if (cacheManager !is null) setCacheManager(cacheManager);
112         if (matcher !is null) setCredentialsMatcher(matcher);
113 
114         this.authorizationCachingEnabled = true;
115         this.permissionResolver = new WildcardPermissionResolver();
116 
117         
118         int instanceNumber = atomicOp!("+=")(INSTANCE_COUNT, 1);
119         instanceNumber--;
120         this.authorizationCacheName = typeid(this).name ~ DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
121         if (instanceNumber > 0) {
122             this.authorizationCacheName = this.authorizationCacheName ~ "." ~ instanceNumber.to!string();
123         }
124     }
125 
126     /*-------------------------------------------
127     |  A C C E S S O R S / M O D I F I E R S    |
128     ============================================*/
129 
130     override void setName(string name) {
131         super.setName(name);
132         string authzCacheName = this.authorizationCacheName;
133         if (authzCacheName !is null && authzCacheName.startsWith(typeid(this).name)) {
134             //get rid of the default class-name based cache name.  Create a more meaningful one
135             //based on the application-unique Realm name:
136             this.authorizationCacheName = name ~ DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
137         }
138     }
139 
140     void setAuthorizationCache(Cache!(Object, AuthorizationInfo) authorizationCache) {
141         this.authorizationCache = authorizationCache;
142     }
143 
144     Cache!(Object, AuthorizationInfo) getAuthorizationCache() {
145         return this.authorizationCache;
146     }
147 
148     string getAuthorizationCacheName() {
149         return authorizationCacheName;
150     }
151 
152     //@SuppressWarnings({"UnusedDeclaration"})
153     void setAuthorizationCacheName(string authorizationCacheName) {
154         this.authorizationCacheName = authorizationCacheName;
155     }
156 
157     /**
158      * Returns {@code true} if authorization caching should be utilized if a {@link CacheManager} has been
159      * {@link #setCacheManager(hunt.shiro.cache.CacheManager) configured}, {@code false} otherwise.
160      * <p/>
161      * The default value is {@code true}.
162      *
163      * @return {@code true} if authorization caching should be utilized, {@code false} otherwise.
164      */
165     bool isAuthorizationCachingEnabled() {
166         version(HUNT_SHIRO_DEBUG) {
167             tracef("authorizationCachingEnabled=%s, isCachingEnabled=%s", 
168                 authorizationCachingEnabled, isCachingEnabled());
169         }
170         return isCachingEnabled() && authorizationCachingEnabled;
171     }
172 
173     /**
174      * Sets whether or not authorization caching should be utilized if a {@link CacheManager} has been
175      * {@link #setCacheManager(hunt.shiro.cache.CacheManager) configured}, {@code false} otherwise.
176      * <p/>
177      * The default value is {@code true}.
178      *
179      * @param authenticationCachingEnabled the value to set
180      */
181     //@SuppressWarnings({"UnusedDeclaration"})
182     void setAuthorizationCachingEnabled(bool authenticationCachingEnabled) {
183         this.authorizationCachingEnabled = authenticationCachingEnabled;
184         if (authenticationCachingEnabled) {
185             setCachingEnabled(true);
186         }
187     }
188 
189     PermissionResolver getPermissionResolver() {
190         return permissionResolver;
191     }
192 
193     void setPermissionResolver(PermissionResolver permissionResolver) {
194         if (permissionResolver  is null) throw new IllegalArgumentException("Null PermissionResolver is not allowed");
195         this.permissionResolver = permissionResolver;
196     }
197 
198     RolePermissionResolver getRolePermissionResolver() {
199         return permissionRoleResolver;
200     }
201 
202     void setRolePermissionResolver(RolePermissionResolver permissionRoleResolver) {
203         this.permissionRoleResolver = permissionRoleResolver;
204     }
205 
206     /*--------------------------------------------
207     |               M E T H O D S               |
208     ============================================*/
209 
210     /**
211      * Initializes this realm and potentially enables a cache, depending on configuration.
212      * <p/>
213      * When this method is called, the following logic is executed:
214      * <ol>
215      * <li>If the {@link #setAuthorizationCache cache} property has been set, it will be
216      * used to cache the AuthorizationInfo objects returned from {@link #getAuthorizationInfo}
217      * method invocations.
218      * All future calls to {@code getAuthorizationInfo} will attempt to use this cache first
219      * to alleviate any potentially unnecessary calls to an underlying data store.</li>
220      * <li>If the {@link #setAuthorizationCache cache} property has <b>not</b> been set,
221      * the {@link #setCacheManager cacheManager} property will be checked.
222      * If a {@code cacheManager} has been set, it will be used to create an authorization
223      * {@code cache}, and this newly created cache which will be used as specified in #1.</li>
224      * <li>If neither the {@link #setAuthorizationCache (hunt.shiro.cache.Cache) cache}
225      * or {@link #setCacheManager(hunt.shiro.cache.CacheManager) cacheManager}
226      * properties are set, caching will be disabled and authorization look-ups will be delegated to
227      * subclass implementations for each authorization check.</li>
228      * </ol>
229      */
230     override protected void onInit() {
231         super.onInit();
232         //trigger obtaining the authorization cache if possible
233         getAvailableAuthorizationCache();
234     }
235 
236     override protected void afterCacheManagerSet() {
237         super.afterCacheManagerSet();
238         //trigger obtaining the authorization cache if possible
239         getAvailableAuthorizationCache();
240     }
241 
242     private Cache!(Object, AuthorizationInfo) getAuthorizationCacheLazy() {
243 
244         if (this.authorizationCache  is null) {
245 
246             version(HUNT_SHIRO_DEBUG) 
247             {
248                 tracef("No authorizationCache instance set.  Checking for a cacheManager...");
249             }
250 
251             CacheManager cacheManager = getCacheManager();
252 
253             if (cacheManager !is null) {
254                 string cacheName = getAuthorizationCacheName();
255                 version(HUNT_SHIRO_DEBUG) 
256                 {
257                     tracef("CacheManager [" ~ (cast(Object)cacheManager).toString() ~ 
258                             "] has been configured.  Building " ~
259                             "authorization cache named [" ~ cacheName ~ "]");
260                 }
261                 auto acm = cast(AbstractCacheManager!(Object, AuthorizationInfo))cacheManager;
262                 this.authorizationCache =  acm.getCache(cacheName);
263 
264                 version(HUNT_SHIRO_DEBUG) tracef("authorizationCache: %s", this.authorizationCache is null);
265             } else {
266                 version(HUNT_SHIRO_DEBUG) 
267                 {
268                     tracef("No cache or cacheManager properties have been set.  Authorization cache cannot " ~
269                             "be obtained.");
270                 }
271             }
272         }
273 
274         return this.authorizationCache;
275     }
276 
277     private Cache!(Object, AuthorizationInfo) getAvailableAuthorizationCache() {
278         Cache!(Object, AuthorizationInfo) cache = getAuthorizationCache();
279         if (cache  is null && isAuthorizationCachingEnabled()) {
280             cache = getAuthorizationCacheLazy();
281         }
282         return cache;
283     }
284 
285     /**
286      * Returns an account's authorization-specific information for the specified {@code principals},
287      * or {@code null} if no account could be found.  The resulting {@code AuthorizationInfo} object is used
288      * by the other method implementations in this class to automatically perform access control checks for the
289      * corresponding {@code Subject}.
290      * <p/>
291      * This implementation obtains the actual {@code AuthorizationInfo} object from the subclass's
292      * implementation of
293      * {@link #doGetAuthorizationInfo(hunt.shiro.subject.PrincipalCollection) doGetAuthorizationInfo}, and then
294      * caches it for efficient reuse if caching is enabled (see below).
295      * <p/>
296      * Invocations of this method should be thought of as completely orthogonal to acquiring
297      * {@link #getAuthenticationInfo(hunt.shiro.authc.AuthenticationToken) authenticationInfo}, since either could
298      * occur in any order.
299      * <p/>
300      * For example, in &quot;Remember Me&quot; scenarios, the user identity is remembered (and
301      * assumed) for their current session and an authentication attempt during that session might never occur.
302      * But because their identity would be remembered, that is sufficient enough information to call this method to
303      * execute any necessary authorization checks.  For this reason, authentication and authorization should be
304      * loosely coupled and not depend on each other.
305      * <h3>Caching</h3>
306      * The {@code AuthorizationInfo} values returned from this method are cached for efficient reuse
307      * if caching is enabled.  Caching is enabled automatically when an {@link #setAuthorizationCache authorizationCache}
308      * instance has been explicitly configured, or if a {@link #setCacheManager cacheManager} has been configured, which
309      * will be used to lazily create the {@code authorizationCache} as needed.
310      * <p/>
311      * If caching is enabled, the authorization cache will be checked first and if found, will return the cached
312      * {@code AuthorizationInfo} immediately.  If caching is disabled, or there is a cache miss, the authorization
313      * info will be looked up from the underlying data store via the
314      * {@link #doGetAuthorizationInfo(hunt.shiro.subject.PrincipalCollection)} method, which must be implemented
315      * by subclasses.
316      * <h4>Changed Data</h4>
317      * If caching is enabled and if any authorization data for an account is changed at
318      * runtime, such as adding or removing roles and/or permissions, the subclass implementation should clear the
319      * cached AuthorizationInfo for that account via the
320      * {@link #clearCachedAuthorizationInfo(hunt.shiro.subject.PrincipalCollection) clearCachedAuthorizationInfo}
321      * method.  This ensures that the next call to {@code getAuthorizationInfo(PrincipalCollection)} will
322      * acquire the account's fresh authorization data, where it will then be cached for efficient reuse.  This
323      * ensures that stale authorization data will not be reused.
324      *
325      * @param principals the corresponding Subject's identifying principals with which to look up the Subject's
326      *                   {@code AuthorizationInfo}.
327      * @return the authorization information for the account associated with the specified {@code principals},
328      *         or {@code null} if no account could be found.
329      */
330     protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
331 
332         if (principals is null) {
333             return null;
334         }
335 
336         AuthorizationInfo info = null;
337 
338         version(HUNT_SHIRO_DEBUG_MORE) {
339             tracef("Retrieving AuthorizationInfo for principals [" ~ (cast(Object)principals).toString() ~ "]");
340         }
341 
342         Cache!(Object, AuthorizationInfo) cache = getAvailableAuthorizationCache();
343 
344         if (cache !is null) {
345             version(HUNT_SHIRO_DEBUG_MORE) {
346                 tracef("Attempting to retrieve the AuthorizationInfo from cache.");
347             }
348             Object key = getAuthorizationCacheKey(principals);
349             info = cache.get(key, null);
350             version(HUNT_SHIRO_DEBUG_MORE) {
351                 if (info  is null) {
352                     tracef("No AuthorizationInfo found in cache for principals [" ~ 
353                         (cast(Object)principals).toString() ~ "]");
354                 } else {
355                     tracef("AuthorizationInfo found in cache for principals [" ~ 
356                         (cast(Object)principals).toString() ~ "]");
357                 }
358             }
359         }
360 
361 
362         if (info is null) {
363             // Call template method if the info was not found in a cache
364             info = doGetAuthorizationInfo(principals);
365             // If the info is not null and the cache has been created, then cache the authorization info.
366             if (info !is null && cache !is null) {
367                 version(HUNT_SHIRO_DEBUG) {
368                     tracef("Caching authorization info for principals: [" ~ 
369                         (cast(Object)principals).toString() ~ "].");
370                 }
371                 Object key = getAuthorizationCacheKey(principals);
372                 cache.put(key, info);
373             }
374         }
375 
376         return info;
377     }
378 
379     protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
380         return cast(Object)principals;
381     }
382 
383     /**
384      * Clears out the AuthorizationInfo cache entry for the specified account.
385      * <p/>
386      * This method is provided as a convenience to subclasses so they can invalidate a cache entry when they
387      * change an account's authorization data (add/remove roles or permissions) during runtime.  Because an account's
388      * AuthorizationInfo can be cached, there needs to be a way to invalidate the cache for only that account so that
389      * subsequent authorization operations don't used the (old) cached value if account data changes.
390      * <p/>
391      * After this method is called, the next authorization check for that same account will result in a call to
392      * {@link #getAuthorizationInfo(hunt.shiro.subject.PrincipalCollection) getAuthorizationInfo}, and the
393      * resulting return value will be cached before being returned so it can be reused for later authorization checks.
394      * <p/>
395      * If you wish to clear out all associated cached data (and not just authorization data), use the
396      * {@link #clearCache(hunt.shiro.subject.PrincipalCollection)} method instead (which will in turn call this
397      * method by default).
398      *
399      * @param principals the principals of the account for which to clear the cached AuthorizationInfo.
400      */
401     protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
402         if (principals  is null) {
403             return;
404         }
405 
406         Cache!(Object, AuthorizationInfo) cache = getAvailableAuthorizationCache();
407         //cache instance will be non-null if caching is enabled:
408         if (cache !is null) {
409             Object key = getAuthorizationCacheKey(principals);
410             cache.remove(key);
411         }
412     }
413 
414     /**
415      * Retrieves the AuthorizationInfo for the given principals from the underlying data store.  When returning
416      * an instance from this method, you might want to consider using an instance of
417      * {@link hunt.shiro.authz.SimpleAuthorizationInfo SimpleAuthorizationInfo}, as it is suitable in most cases.
418      *
419      * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
420      * @return the AuthorizationInfo associated with this principals.
421      * @see hunt.shiro.authz.SimpleAuthorizationInfo
422      */
423     protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
424 
425     //visibility changed from private to protected per SHIRO-332
426     protected Collection!(Permission) getPermissions(AuthorizationInfo info) {
427         Set!(Permission) permissions = new HashSet!(Permission)();
428 
429         if (info !is null) {
430             Collection!(Permission) perms = info.getObjectPermissions();
431             if (!CollectionUtils.isEmpty(perms)) {
432                 permissions.addAll(perms);
433             }
434             perms = resolvePermissions(info.getStringPermissions());
435             if (!CollectionUtils.isEmpty(perms)) {
436                 permissions.addAll(perms);
437             }
438 
439             perms = resolveRolePermissions(info.getRoles());
440             if (!CollectionUtils.isEmpty(perms)) {
441                 permissions.addAll(perms);
442             }
443         }
444 
445         if (permissions.isEmpty()) {
446             return Collections.emptySet!(Permission)();
447         } else {
448             return permissions;
449         }
450     }
451 
452     private Collection!(Permission) resolvePermissions(Collection!(string) stringPerms) {
453         Collection!(Permission) perms = Collections.emptySet!Permission();
454         PermissionResolver resolver = getPermissionResolver();
455         if (resolver !is null && !CollectionUtils.isEmpty(stringPerms)) {
456             perms = new LinkedHashSet!(Permission)(stringPerms.size());
457             foreach(string strPermission ; stringPerms) {
458                 Permission permission = resolver.resolvePermission(strPermission);
459                 perms.add(permission);
460             }
461         }
462         return perms;
463     }
464 
465     private Collection!(Permission) resolveRolePermissions(Collection!(string) roleNames) {
466         Collection!(Permission) perms = Collections.emptySet!Permission();
467         RolePermissionResolver resolver = getRolePermissionResolver();
468         if (resolver !is null && !CollectionUtils.isEmpty(roleNames)) {
469             perms = new LinkedHashSet!(Permission)(roleNames.size());
470             foreach(string roleName ; roleNames) {
471                 Collection!(Permission) resolved = resolver.resolvePermissionsInRole(roleName);
472                 if (!CollectionUtils.isEmpty(resolved)) {
473                     perms.addAll(resolved);
474                 }
475             }
476         }
477         return perms;
478     }
479 
480     bool isPermitted(PrincipalCollection principals, string permission) {
481         try {
482             Permission p = getPermissionResolver().resolvePermission(permission);
483             return isPermitted(principals, p);
484         } catch(Exception ex) {
485             warning(ex.msg);
486             version(HUNT_DEBUG) warning(ex);
487             return false;
488         }
489     }
490 
491     bool isPermitted(PrincipalCollection principals, Permission permission) {
492         try {
493             AuthorizationInfo info = getAuthorizationInfo(principals);
494             return isPermitted(permission, info);
495         } catch(Exception ex) {
496             warning(ex.msg);
497             version(HUNT_SHIRO_DEBUG) warning(ex);
498             return false;
499         }
500     }
501 
502     //visibility changed from private to protected per SHIRO-332
503     protected bool isPermitted(Permission permission, AuthorizationInfo info) {
504         Collection!(Permission) perms = getPermissions(info);
505         if (perms !is null && !perms.isEmpty()) {
506             foreach (Permission perm; perms) {
507                 if (perm.implies(permission)) {
508                     return true;
509                 }
510             }
511         }
512         return false;
513     }
514 
515     bool[] isPermitted(PrincipalCollection subjectIdentifier, string[] permissions...) {
516         List!(Permission) perms = new ArrayList!(Permission)(cast(int) permissions.length);
517         foreach (string permString; permissions) {
518             perms.add(getPermissionResolver().resolvePermission(permString));
519         }
520         return isPermitted(subjectIdentifier, perms);
521     }
522 
523     bool[] isPermitted(PrincipalCollection principals, List!(Permission) permissions) {
524         AuthorizationInfo info = getAuthorizationInfo(principals);
525         return isPermitted(permissions, info);
526     }
527 
528     protected bool[] isPermitted(List!(Permission) permissions, AuthorizationInfo info) {
529         bool[] result;
530         if (permissions !is null && !permissions.isEmpty()) {
531             int size = permissions.size();
532             result = new bool[size];
533             int i = 0;
534             foreach (Permission p; permissions) {
535                 result[i++] = isPermitted(p, info);
536             }
537         } else {
538             result = new bool[0];
539         }
540         return result;
541     }
542 
543     bool isPermittedAll(PrincipalCollection subjectIdentifier, string[] permissions...) {
544         if (permissions !is null && permissions.length > 0) {
545             Collection!(Permission) perms = new ArrayList!(Permission)(cast(int) permissions.length);
546             foreach (string permString; permissions) {
547                 perms.add(getPermissionResolver().resolvePermission(permString));
548             }
549             return isPermittedAll(subjectIdentifier, perms);
550         }
551         return false;
552     }
553 
554     bool isPermittedAll(PrincipalCollection principal, Collection!(Permission) permissions) {
555         AuthorizationInfo info = getAuthorizationInfo(principal);
556         return info !is null && isPermittedAll(permissions, info);
557     }
558 
559     protected bool isPermittedAll(Collection!(Permission) permissions, AuthorizationInfo info) {
560         if (permissions !is null && !permissions.isEmpty()) {
561             foreach (Permission p; permissions) {
562                 if (!isPermitted(p, info)) {
563                     return false;
564                 }
565             }
566         }
567         return true;
568     }
569 
570     void checkPermission(PrincipalCollection subjectIdentifier, string permission) {
571         Permission p = getPermissionResolver().resolvePermission(permission);
572         checkPermission(subjectIdentifier, p);
573     }
574 
575     void checkPermission(PrincipalCollection principal, Permission permission) {
576         AuthorizationInfo info = getAuthorizationInfo(principal);
577         checkPermission(permission, info);
578     }
579 
580     protected void checkPermission(Permission permission, AuthorizationInfo info) {
581         if (!isPermitted(permission, info)) {
582             string msg = "User is not permitted [" ~ (cast(Object) permission).toString() ~ "]";
583             throw new UnauthorizedException(msg);
584         }
585     }
586 
587     void checkPermissions(PrincipalCollection subjectIdentifier, string[] permissions...) {
588         if (permissions !is null) {
589             foreach (string permString; permissions) {
590                 checkPermission(subjectIdentifier, permString);
591             }
592         }
593     }
594 
595     void checkPermissions(PrincipalCollection principal, Collection!(Permission) permissions) {
596         AuthorizationInfo info = getAuthorizationInfo(principal);
597         checkPermissions(permissions, info);
598     }
599 
600     protected void checkPermissions(Collection!(Permission) permissions, AuthorizationInfo info) {
601         if (permissions !is null && !permissions.isEmpty()) {
602             foreach (Permission p; permissions) {
603                 checkPermission(p, info);
604             }
605         }
606     }
607 
608     bool hasRole(PrincipalCollection principal, string roleIdentifier) {
609         AuthorizationInfo info = getAuthorizationInfo(principal);
610         return hasRole(roleIdentifier, info);
611     }
612 
613     protected bool hasRole(string roleIdentifier, AuthorizationInfo info) {
614         // return info !is null && info.getRoles() !is null && info.getRoles().contains(roleIdentifier);
615 
616         version (HUNT_SHIRO_DEBUG)
617             tracef("checking: %s", roleIdentifier);
618         if (info !is null) {
619             Collection!(string) roles = info.getRoles();
620             if (roles !is null) {
621                 bool r = roles.contains(roleIdentifier);
622                 version (HUNT_SHIRO_DEBUG) {
623                     infof("roles: %s, result: %s", roles, r);
624                 }
625                 return r;
626             }
627         }
628         return false;
629     }
630 
631     bool[] hasRoles(PrincipalCollection principal, List!(string) roleIdentifiers) {
632         AuthorizationInfo info = getAuthorizationInfo(principal);
633         if (roleIdentifiers is null || roleIdentifiers.isEmpty()) {
634             return new bool[0];
635         }
636 
637         return hasRoles(roleIdentifiers.toArray(), info);
638     }
639 
640     bool[] hasRoles(PrincipalCollection principal, string[] roleIdentifiers) {
641         AuthorizationInfo info = getAuthorizationInfo(principal);
642         bool[] result = new bool[roleIdentifiers.length];
643         if (info !is null) {
644             result = hasRoles(roleIdentifiers, info);
645         }
646         return result;
647     }
648 
649     // protected bool[] hasRoles(List!(string) roleIdentifiers, AuthorizationInfo info) {
650     //     bool[] result;
651     //     if (roleIdentifiers is null || roleIdentifiers.isEmpty()) {
652     //         result = new bool[0];
653     //     } else {
654     //         result = hasRoles(roleIdentifiers.toArray(), info);
655     //     }
656     //     return result;
657     // }
658 
659     protected bool[] hasRoles(string[] roleIdentifiers, AuthorizationInfo info) {
660         bool[] result;
661         if (roleIdentifiers.empty()) {
662             result = new bool[0];
663         } else {
664             result = new bool[roleIdentifiers.length];
665             int i = 0;
666             foreach (string roleName; roleIdentifiers) {
667                 result[i++] = hasRole(roleName, info);
668             }
669         }
670         return result;
671     }
672 
673     bool hasAllRoles(PrincipalCollection principal, Collection!(string) roleIdentifiers) {
674         AuthorizationInfo info = getAuthorizationInfo(principal);
675         return info !is null && hasAllRoles(roleIdentifiers, info);
676     }
677 
678     bool hasAllRoles(PrincipalCollection principal, string[] roleIdentifiers) {
679         AuthorizationInfo info = getAuthorizationInfo(principal);
680         return info !is null && hasAllRoles(roleIdentifiers, info);
681     }
682 
683     private bool hasAllRoles(Collection!(string) roleIdentifiers, AuthorizationInfo info) {
684         if (roleIdentifiers !is null && !roleIdentifiers.isEmpty()) {
685             return hasAllRoles(roleIdentifiers.toArray(), info);
686         }
687         return true;
688     }
689 
690     private bool hasAllRoles(string[] roleIdentifiers, AuthorizationInfo info) {
691         foreach (string roleName; roleIdentifiers) {
692             if (!hasRole(roleName, info)) {
693                 return false;
694             }
695         }
696         return true;
697     }
698 
699     void checkRole(PrincipalCollection principal, string role) {
700         AuthorizationInfo info = getAuthorizationInfo(principal);
701         checkRole(role, info);
702     }
703 
704     protected void checkRole(string role, AuthorizationInfo info) {
705         if (!hasRole(role, info)) {
706             string msg = "User does not have role [" ~ role ~ "]";
707             throw new UnauthorizedException(msg);
708         }
709     }
710 
711     void checkRoles(PrincipalCollection principal, Collection!(string) roles) {
712         AuthorizationInfo info = getAuthorizationInfo(principal);
713         checkRoles(roles, info);
714     }
715 
716     void checkRoles(PrincipalCollection principal, string[] roles...) {
717 
718         AuthorizationInfo info = getAuthorizationInfo(principal);
719         if (!roles.empty()) {
720             foreach (string roleName; roles) {
721                 checkRole(roleName, info);
722             }
723         }
724     }
725 
726     protected void checkRoles(Collection!(string) roles, AuthorizationInfo info) {
727         if (roles !is null && !roles.isEmpty()) {
728             foreach (string roleName; roles) {
729                 checkRole(roleName, info);
730             }
731         }
732     }
733 
734     /**
735      * Calls {@code super.doClearCache} to ensure any cached authentication data is removed and then calls
736      * {@link #clearCachedAuthorizationInfo(hunt.shiro.subject.PrincipalCollection)} to remove any cached
737      * authorization data.
738      * <p/>
739      * If overriding in a subclass, be sure to call {@code super.doClearCache} to ensure this behavior is maintained.
740      *
741      * @param principals the principals of the account for which to clear any cached AuthorizationInfo
742      */
743     override protected void doClearCache(PrincipalCollection principals) {
744         super.doClearCache(principals);
745         clearCachedAuthorizationInfo(principals);
746     }
747 }