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