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