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 "Remember Me" 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 }