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