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 */
019package org.apache.shiro.realm;
020
021import org.apache.shiro.authc.credential.CredentialsMatcher;
022import org.apache.shiro.authz.*;
023import org.apache.shiro.authz.permission.*;
024import org.apache.shiro.cache.Cache;
025import org.apache.shiro.cache.CacheManager;
026import org.apache.shiro.subject.PrincipalCollection;
027import org.apache.shiro.util.CollectionUtils;
028import org.apache.shiro.util.Initializable;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import java.util.*;
033import 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 */
055public 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.isDebugEnabled()) {
248                    log.debug("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    //changed visibility from private to protected for SHIRO-332
402    protected Collection<Permission> getPermissions(AuthorizationInfo info) {
403        Set<Permission> permissions = new HashSet<Permission>();
404
405        if (info != null) {
406            Collection<Permission> perms = info.getObjectPermissions();
407            if (!CollectionUtils.isEmpty(perms)) {
408                permissions.addAll(perms);
409            }
410            perms = resolvePermissions(info.getStringPermissions());
411            if (!CollectionUtils.isEmpty(perms)) {
412                permissions.addAll(perms);
413            }
414
415            perms = resolveRolePermissions(info.getRoles());
416            if (!CollectionUtils.isEmpty(perms)) {
417                permissions.addAll(perms);
418            }
419        }
420
421        if (permissions.isEmpty()) {
422            return Collections.emptySet();
423        } else {
424            return Collections.unmodifiableSet(permissions);
425        }
426    }
427
428    private Collection<Permission> resolvePermissions(Collection<String> stringPerms) {
429        Collection<Permission> perms = Collections.emptySet();
430        PermissionResolver resolver = getPermissionResolver();
431        if (resolver != null && !CollectionUtils.isEmpty(stringPerms)) {
432            perms = new LinkedHashSet<Permission>(stringPerms.size());
433            for (String strPermission : stringPerms) {
434                Permission permission = getPermissionResolver().resolvePermission(strPermission);
435                perms.add(permission);
436            }
437        }
438        return perms;
439    }
440
441    private Collection<Permission> resolveRolePermissions(Collection<String> roleNames) {
442        Collection<Permission> perms = Collections.emptySet();
443        RolePermissionResolver resolver = getRolePermissionResolver();
444        if (resolver != null && !CollectionUtils.isEmpty(roleNames)) {
445            perms = new LinkedHashSet<Permission>(roleNames.size());
446            for (String roleName : roleNames) {
447                Collection<Permission> resolved = resolver.resolvePermissionsInRole(roleName);
448                if (!CollectionUtils.isEmpty(resolved)) {
449                    perms.addAll(resolved);
450                }
451            }
452        }
453        return perms;
454    }
455
456    public boolean isPermitted(PrincipalCollection principals, String permission) {
457        Permission p = getPermissionResolver().resolvePermission(permission);
458        return isPermitted(principals, p);
459    }
460
461    public boolean isPermitted(PrincipalCollection principals, Permission permission) {
462        AuthorizationInfo info = getAuthorizationInfo(principals);
463        return isPermitted(permission, info);
464    }
465
466    //changed visibility from private to protected for SHIRO-332
467    protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
468        Collection<Permission> perms = getPermissions(info);
469        if (perms != null && !perms.isEmpty()) {
470            for (Permission perm : perms) {
471                if (perm.implies(permission)) {
472                    return true;
473                }
474            }
475        }
476        return false;
477    }
478
479    public boolean[] isPermitted(PrincipalCollection subjectIdentifier, String... permissions) {
480        List<Permission> perms = new ArrayList<Permission>(permissions.length);
481        for (String permString : permissions) {
482            perms.add(getPermissionResolver().resolvePermission(permString));
483        }
484        return isPermitted(subjectIdentifier, perms);
485    }
486
487    public boolean[] isPermitted(PrincipalCollection principals, List<Permission> permissions) {
488        AuthorizationInfo info = getAuthorizationInfo(principals);
489        return isPermitted(permissions, info);
490    }
491
492    protected boolean[] isPermitted(List<Permission> permissions, AuthorizationInfo info) {
493        boolean[] result;
494        if (permissions != null && !permissions.isEmpty()) {
495            int size = permissions.size();
496            result = new boolean[size];
497            int i = 0;
498            for (Permission p : permissions) {
499                result[i++] = isPermitted(p, info);
500            }
501        } else {
502            result = new boolean[0];
503        }
504        return result;
505    }
506
507    public boolean isPermittedAll(PrincipalCollection subjectIdentifier, String... permissions) {
508        if (permissions != null && permissions.length > 0) {
509            Collection<Permission> perms = new ArrayList<Permission>(permissions.length);
510            for (String permString : permissions) {
511                perms.add(getPermissionResolver().resolvePermission(permString));
512            }
513            return isPermittedAll(subjectIdentifier, perms);
514        }
515        return false;
516    }
517
518    public boolean isPermittedAll(PrincipalCollection principal, Collection<Permission> permissions) {
519        AuthorizationInfo info = getAuthorizationInfo(principal);
520        return info != null && isPermittedAll(permissions, info);
521    }
522
523    protected boolean isPermittedAll(Collection<Permission> permissions, AuthorizationInfo info) {
524        if (permissions != null && !permissions.isEmpty()) {
525            for (Permission p : permissions) {
526                if (!isPermitted(p, info)) {
527                    return false;
528                }
529            }
530        }
531        return true;
532    }
533
534    public void checkPermission(PrincipalCollection subjectIdentifier, String permission) throws AuthorizationException {
535        Permission p = getPermissionResolver().resolvePermission(permission);
536        checkPermission(subjectIdentifier, p);
537    }
538
539    public void checkPermission(PrincipalCollection principal, Permission permission) throws AuthorizationException {
540        AuthorizationInfo info = getAuthorizationInfo(principal);
541        checkPermission(permission, info);
542    }
543
544    protected void checkPermission(Permission permission, AuthorizationInfo info) {
545        if (!isPermitted(permission, info)) {
546            String msg = "User is not permitted [" + permission + "]";
547            throw new UnauthorizedException(msg);
548        }
549    }
550
551    public void checkPermissions(PrincipalCollection subjectIdentifier, String... permissions) throws AuthorizationException {
552        if (permissions != null) {
553            for (String permString : permissions) {
554                checkPermission(subjectIdentifier, permString);
555            }
556        }
557    }
558
559    public void checkPermissions(PrincipalCollection principal, Collection<Permission> permissions) throws AuthorizationException {
560        AuthorizationInfo info = getAuthorizationInfo(principal);
561        checkPermissions(permissions, info);
562    }
563
564    protected void checkPermissions(Collection<Permission> permissions, AuthorizationInfo info) {
565        if (permissions != null && !permissions.isEmpty()) {
566            for (Permission p : permissions) {
567                checkPermission(p, info);
568            }
569        }
570    }
571
572    public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
573        AuthorizationInfo info = getAuthorizationInfo(principal);
574        return hasRole(roleIdentifier, info);
575    }
576
577    protected boolean hasRole(String roleIdentifier, AuthorizationInfo info) {
578        return info != null && info.getRoles() != null && info.getRoles().contains(roleIdentifier);
579    }
580
581    public boolean[] hasRoles(PrincipalCollection principal, List<String> roleIdentifiers) {
582        AuthorizationInfo info = getAuthorizationInfo(principal);
583        boolean[] result = new boolean[roleIdentifiers != null ? roleIdentifiers.size() : 0];
584        if (info != null) {
585            result = hasRoles(roleIdentifiers, info);
586        }
587        return result;
588    }
589
590    protected boolean[] hasRoles(List<String> roleIdentifiers, AuthorizationInfo info) {
591        boolean[] result;
592        if (roleIdentifiers != null && !roleIdentifiers.isEmpty()) {
593            int size = roleIdentifiers.size();
594            result = new boolean[size];
595            int i = 0;
596            for (String roleName : roleIdentifiers) {
597                result[i++] = hasRole(roleName, info);
598            }
599        } else {
600            result = new boolean[0];
601        }
602        return result;
603    }
604
605    public boolean hasAllRoles(PrincipalCollection principal, Collection<String> roleIdentifiers) {
606        AuthorizationInfo info = getAuthorizationInfo(principal);
607        return info != null && hasAllRoles(roleIdentifiers, info);
608    }
609
610    private boolean hasAllRoles(Collection<String> roleIdentifiers, AuthorizationInfo info) {
611        if (roleIdentifiers != null && !roleIdentifiers.isEmpty()) {
612            for (String roleName : roleIdentifiers) {
613                if (!hasRole(roleName, info)) {
614                    return false;
615                }
616            }
617        }
618        return true;
619    }
620
621    public void checkRole(PrincipalCollection principal, String role) throws AuthorizationException {
622        AuthorizationInfo info = getAuthorizationInfo(principal);
623        checkRole(role, info);
624    }
625
626    protected void checkRole(String role, AuthorizationInfo info) {
627        if (!hasRole(role, info)) {
628            String msg = "User does not have role [" + role + "]";
629            throw new UnauthorizedException(msg);
630        }
631    }
632
633    public void checkRoles(PrincipalCollection principal, Collection<String> roles) throws AuthorizationException {
634        AuthorizationInfo info = getAuthorizationInfo(principal);
635        checkRoles(roles, info);
636    }
637
638    public void checkRoles(PrincipalCollection principal, String... roles) throws AuthorizationException {
639        checkRoles(principal, Arrays.asList(roles));
640    }
641
642    protected void checkRoles(Collection<String> roles, AuthorizationInfo info) {
643        if (roles != null && !roles.isEmpty()) {
644            for (String roleName : roles) {
645                checkRole(roleName, info);
646            }
647        }
648    }
649
650    /**
651     * Calls {@code super.doClearCache} to ensure any cached authentication data is removed and then calls
652     * {@link #clearCachedAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)} to remove any cached
653     * authorization data.
654     * <p/>
655     * If overriding in a subclass, be sure to call {@code super.doClearCache} to ensure this behavior is maintained.
656     *
657     * @param principals the principals of the account for which to clear any cached AuthorizationInfo
658     * @since 1.2
659     */
660    @Override
661    protected void doClearCache(PrincipalCollection principals) {
662        super.doClearCache(principals);
663        clearCachedAuthorizationInfo(principals);
664    }
665}