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