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