blob: 0b7b738aa320a584ef105a0e741c8bdc7f8cfb59 [file] [log] [blame]
/*
* Copyright 2005-2008 Les Hazlewood, Jeremy Haile
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jsecurity.realm;
import org.jsecurity.authc.Account;
import org.jsecurity.authc.credential.CredentialsMatcher;
import org.jsecurity.authz.*;
import org.jsecurity.authz.permission.PermissionResolver;
import org.jsecurity.authz.permission.PermissionResolverAware;
import org.jsecurity.authz.permission.WildcardPermissionResolver;
import org.jsecurity.cache.Cache;
import org.jsecurity.cache.CacheManager;
import org.jsecurity.util.Initializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* An <tt>AuthorizingRealm</tt> extends the <tt>AuthenticatingRealm</tt>'s capabilities by adding Authorization
* (access control) support via the use of {@link AuthorizingAccount AuthorizingAccount} instances.
*
* <p>This implementation can only support Authorization operations if the subclass implementation's
* {@link #getAccount(Object) getAccount(principal)} method returns an {@link AuthorizingAccount AuthorizingAccount}.
* <p>If it does not, subclasses <em>must</em> override all {@link org.jsecurity.authz.Authorizer Authorizer} methods,
* since the JSecurity default implementations cannot infer Role/Permission assignments via anything but
* <tt>AuthorizingAccount</tt> instances.
*
* <p>If your Realm implementation does not want to deal with <tt>AuthorizingAccount</tt> constructs, you are of course
* free to subclass the {@link AuthorizingRealm AuthorizingRealm} directly and implement the remaining
* interface methods yourself. Many people do this if they want to have better control over how the
* Role and Permission checks occur for their specific data source.
*
* @author Les Hazlewood
* @author Jeremy Haile
* @since 0.2
*/
public abstract class AuthorizingRealm extends AuthenticatingRealm implements Initializable, PermissionResolverAware {
/*--------------------------------------------
| C O N S T A N T S |
============================================*/
/**
* The default postfix appended to the realm name for caching Accounts.
*/
private static final String DEFAULT_ACCOUNT_CACHE_POSTFIX = "-accounts";
private static int INSTANCE_COUNT = 0;
/*--------------------------------------------
| I N S T A N C E V A R I A B L E S |
============================================*/
/**
* The cache used by this realm to store Accounts associated with individual Subject principals.
*/
private Cache accountCache = null;
private String accountCacheName = null;
private PermissionResolver permissionResolver = new WildcardPermissionResolver();
/*--------------------------------------------
| C O N S T R U C T O R S |
============================================*/
public AuthorizingRealm() {
}
public AuthorizingRealm(CacheManager cacheManager) {
super(cacheManager);
}
public AuthorizingRealm(CredentialsMatcher matcher) {
super(matcher);
}
public AuthorizingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
super(cacheManager, matcher);
}
/*--------------------------------------------
| A C C E S S O R S / M O D I F I E R S |
============================================*/
public void setAccountCache(Cache accountCache) {
this.accountCache = accountCache;
}
public Cache getAccountCache() {
return this.accountCache;
}
public String getAccountCacheName() {
return accountCacheName;
}
public void setAccountCacheName(String accountCacheName) {
this.accountCacheName = accountCacheName;
}
public PermissionResolver getPermissionResolver() {
return permissionResolver;
}
public void setPermissionResolver(PermissionResolver permissionResolver) {
this.permissionResolver = permissionResolver;
}
/*--------------------------------------------
| M E T H O D S |
============================================*/
/**
* Initializes this realm and potentially enables a cache, depending on configuration.
*
* <p>When this method is called, the following logic is executed:
* <ol>
* <li>If the {@link #setAccountCache cache} property has been set, it will be
* used to cache the Account objects returned from {@link #getAccount getAccount}
* method invocations.
* All future calls to <tt>getAccount</tt> will attempt to use this Account cache first
* to alleviate any potentially unnecessary calls to an underlying data store.</li>
* <li>If the {@link #setAccountCache cache} property has <b>not</b> been set,
* the {@link #setCacheManager cacheManager} property will be checked.
* If a <tt>cacheManager</tt> has been set, it will be used to create an Account
* <tt>cache</tt>, and this newly created cache which will be used as specified in #1.</li>
* <li>If neither the {@link #setAccountCache(org.jsecurity.cache.Cache) cache}
* or {@link #setCacheManager(org.jsecurity.cache.CacheManager) cacheManager}
* properties are set, caching will be disabled and Account lookups will be delegated to
* subclass implementations for each authorization check.</li>
* </ol>
*/
public final void init() {
initAccountCache();
afterAccountCacheSet();
}
protected void afterAccountCacheSet() {
}
protected void initAccountCache() {
if (log.isTraceEnabled()) {
log.trace("Initializing account cache.");
}
Cache cache = getAccountCache();
if (cache == null) {
if (log.isDebugEnabled()) {
log.debug("No cache implementation set. Checking cacheManager...");
}
CacheManager cacheManager = getCacheManager();
if (cacheManager != null) {
String cacheName = getAccountCacheName();
if (cacheName == null) {
//Simple default in case they didn't provide one:
cacheName = getClass().getName() + "-" + INSTANCE_COUNT++ + DEFAULT_ACCOUNT_CACHE_POSTFIX;
setAccountCacheName(cacheName);
}
if (log.isDebugEnabled()) {
log.debug("CacheManager [" + cacheManager + "] has been configured. Building " +
"Account cache named [" + cacheName + "]");
}
cache = cacheManager.getCache(cacheName);
setAccountCache(cache);
} else {
if (log.isInfoEnabled()) {
log.info("No cache or cacheManager properties have been set. Account caching is " +
"disabled.");
}
}
}
}
/**
* Template-pattern method to be implemented by subclasses to retrieve the Account for the given principal.
*
* @param principal the primary identifying principal of the Account that should be retrieved.
* @return the Account associated with this principal.
*/
protected abstract AuthorizingAccount doGetAccount(Object principal);
/**
* <p>Retrieves Account information for the given account principal.
*
* <p>If caching is enabled, the account cache will be checked first and if found, will return the cached account.
* If caching is disabled, or there is a cache miss from the cache lookup, the Account will be looked up from
* the underlying data store via the {@link #doGetAccount(Object)} method, which must be implemented by subclasses.
*
* <p>If caching is enabled, the retrieved Account from <tt>doGetAccount</tt> will be added to the account cache
* first and then returned.
*
* @param principal the primary identifying principal of the Account that should be retrieved.
* @return the Account associated with this princpal.
*/
protected Account getAccount(Object principal) {
if (principal == null) {
return null;
}
Account account = null;
if (log.isTraceEnabled()) {
log.trace("Retrieving Account for principal [" + principal + "]");
}
Cache accountCache = getAccountCache();
if (accountCache != null) {
if (log.isTraceEnabled()) {
log.trace("Attempting to retrieve the Account from cache.");
}
account = (Account) accountCache.get(principal);
if (log.isTraceEnabled()) {
if (account == null) {
log.trace("No Account found in cache for principal [" + principal + "]");
} else {
log.trace("Account found in cache for principal [" + principal + "]");
}
}
}
if (account == null) {
// Call template method if tbe Account was not found in a cache
account = doGetAccount(principal);
// If the account is not null and the cache has been created, then cache the account.
if (account != null && accountCache != null) {
if (log.isTraceEnabled()) {
log.trace("Caching Account [" + principal + "].");
}
accountCache.put(principal, account);
}
}
return account;
}
protected AuthorizingAccount getAuthorizingAccount(Object principal) {
if (principal == null) {
throw new AuthorizationException("Specified principal argument is null and authorization checks cannot " +
"occur without a known account identity.");
}
Account account = getAccount(principal);
assertNotNullAccount(principal, account);
assertAuthorizingAccount(account);
return (AuthorizingAccount) account;
}
protected void assertNotNullAccount(Object subjectPrincipal, Account account) {
if (account == null) {
throw new MissingAccountException("No Account found for Subject principal [" +
subjectPrincipal + "]");
}
}
protected void assertAuthorizingAccount(Account account) {
if (!(account instanceof AuthorizingAccount)) {
String msg = "Underlying Account instance [" + account + "] does not implement the " +
AuthorizingAccount.class.getName() + " interface. The JSecurity " +
AuthorizingRealm.class.getName() + " class and its default implementations can only provide default " +
"authorization (access control) support for Accounts that implement this interface. If you do not " +
"wish to implement this interface, you will need to override all of this Realm's Authorizer methods " +
"to perform the authorization check explicitly.\n\nNote that there is nothing wrong with this " +
"approach since it often gives finer control of how authorization checks occur, but you would have " +
"to override these methods explicitly since JSecurity can't infer your application's security " +
"data model.";
throw new UnsupportedAccountException(msg);
}
}
public boolean isPermitted(Object subjectIdentifier, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(subjectIdentifier, p);
}
public boolean isPermitted(Object principal, Permission permission) {
AuthorizingAccount account = getAuthorizingAccount(principal);
return account.isPermitted(permission);
}
public boolean[] isPermitted(Object subjectIdentifier, String... permissions) {
List<Permission> perms = new ArrayList<Permission>(permissions.length);
for (String permString : permissions) {
perms.add(getPermissionResolver().resolvePermission(permString));
}
return isPermitted(subjectIdentifier, perms);
}
public boolean[] isPermitted(Object principal, List<Permission> permissions) {
AuthorizingAccount account = getAuthorizingAccount(principal);
return account.isPermitted(permissions);
}
public boolean isPermittedAll(Object subjectIdentifier, String... permissions) {
if (permissions != null && permissions.length > 0) {
Collection<Permission> perms = new ArrayList<Permission>(permissions.length);
for (String permString : permissions) {
perms.add(getPermissionResolver().resolvePermission(permString));
}
return isPermittedAll(subjectIdentifier, perms);
}
return false;
}
public boolean isPermittedAll(Object principal, Collection<Permission> permissions) {
AuthorizingAccount account = getAuthorizingAccount(principal);
return account != null && account.isPermittedAll(permissions);
}
public void checkPermission(Object subjectIdentifier, String permission) throws AuthorizationException {
Permission p = getPermissionResolver().resolvePermission(permission);
checkPermission(subjectIdentifier, p);
}
public void checkPermission(Object principal, Permission permission) throws AuthorizationException {
AuthorizingAccount account = getAuthorizingAccount(principal);
account.checkPermission(permission);
}
public void checkPermissions(Object subjectIdentifier, String... permissions) throws AuthorizationException {
if (permissions != null) {
for (String permString : permissions) {
checkPermission(subjectIdentifier, permString);
}
}
}
public void checkPermissions(Object principal, Collection<Permission> permissions) throws AuthorizationException {
AuthorizingAccount account = getAuthorizingAccount(principal);
account.checkPermissions(permissions);
}
public boolean hasRole(Object principal, String roleIdentifier) {
AuthorizingAccount account = getAuthorizingAccount(principal);
return account.hasRole(roleIdentifier);
}
public boolean[] hasRoles(Object principal, List<String> roleIdentifiers) {
AuthorizingAccount account = getAuthorizingAccount(principal);
boolean[] result = new boolean[roleIdentifiers != null ? roleIdentifiers.size() : 0];
if (account != null) {
result = account.hasRoles(roleIdentifiers);
}
return result;
}
public boolean hasAllRoles(Object principal, Collection<String> roleIdentifiers) {
AuthorizingAccount account = getAuthorizingAccount(principal);
return account != null && account.hasAllRoles(roleIdentifiers);
}
public void checkRole(Object principal, String role) throws AuthorizationException {
AuthorizingAccount account = getAuthorizingAccount(principal);
account.checkRole(role);
}
public void checkRoles(Object principal, Collection<String> roles) throws AuthorizationException {
AuthorizingAccount account = getAuthorizingAccount(principal);
account.checkRoles(roles);
}
/**
* If account caching is enabled, this will remove the account from the cache. Subclasses are free to override
* for additional behavior, but be sure to call <tt>super.onLogout</tt> to ensure cache cleanup.
*
* @param accountPrincipal the application-specific Subject/user identifier.
*/
public void onLogout(Object accountPrincipal) {
Cache cache = getAccountCache();
//cache instance will be non-null if caching is enabled:
if (cache != null && accountPrincipal != null) {
cache.remove(accountPrincipal);
}
}
}