Merged BRANCHES\DEV\MODEL_SPLIT to HEAD

Split the data dictionary out from the repository.
    There is a new DataModel project to add into eclipse
    Still to move a few context dependent tests


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@20877 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Andrew Hind
2010-06-30 14:17:18 +00:00
parent 3238ca154a
commit 1e19360747
172 changed files with 170 additions and 30303 deletions

View File

@@ -1,72 +0,0 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.security.authentication;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.context.security.SecureContext;
/**
* Extensions for the Alfresco security context.
*
* This is based on the Linux model and supports real, effective and stored authorities
*
* The real authority is used for auditing and reporting who the user is etc.
* The effective authority is used for permission checks.
*
* RunAs support leaves the real authority and changes only the effective authority
* That means "special" code can run code as system but still be audited as Joe
*
* In the future scrips etc can support a setUId flag and run as the owner of the script.
* If the script chooses to do this ....
* A method invocation could do the same (after entry security checks)
*
* TODO: extent runAs to take a nodeRef context - it can then set the stored atc and set this as effective if required.
*
* @author andyh
*
*/
public interface AlfrescoSecureContext extends SecureContext
{
/**
* Get the effective authentication - used for permission checks
* @return
*/
public Authentication getEffectiveAuthentication();
/**
* Get the real authenticaiton - used for auditing and everything else
* @return
*/
public Authentication getRealAuthentication();
/**
* Set the effective authentication held by the context
*
* @param effictiveAuthentication
*/
public void setEffectiveAuthentication(Authentication effictiveAuthentication);
/**
* Set the real authentication held by the context
*
* @param realAuthentication
*/
public void setRealAuthentication(Authentication realAuthentication);
}

View File

@@ -1,150 +0,0 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.security.authentication;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.context.ContextInvalidException;
/**
* Hold an Alfresco extended security context
*
* @author andyh
*
*/
public class AlfrescoSecureContextImpl implements AlfrescoSecureContext
{
private static final long serialVersionUID = -8893133731693272549L;
private Authentication realAuthentication;
private Authentication effectiveAuthentication;
/**
* ACEGI
*/
public Authentication getAuthentication()
{
return getEffectiveAuthentication();
}
/**
* ACEGI
*/
public void setAuthentication(Authentication newAuthentication)
{
setEffectiveAuthentication(newAuthentication);
}
/**
* ACEGI
*/
public void validate() throws ContextInvalidException
{
if (effectiveAuthentication == null)
{
throw new ContextInvalidException("Effective authentication not set");
}
}
public Authentication getEffectiveAuthentication()
{
return effectiveAuthentication;
}
public Authentication getRealAuthentication()
{
return realAuthentication;
}
public void setEffectiveAuthentication(Authentication effictiveAuthentication)
{
this.effectiveAuthentication = effictiveAuthentication;
}
public void setRealAuthentication(Authentication realAuthentication)
{
this.realAuthentication = realAuthentication;
}
@Override
public int hashCode()
{
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((effectiveAuthentication == null) ? 0 : effectiveAuthentication.hashCode());
result = PRIME * result + ((realAuthentication == null) ? 0 : realAuthentication.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final AlfrescoSecureContextImpl other = (AlfrescoSecureContextImpl) obj;
if (effectiveAuthentication == null)
{
if (other.effectiveAuthentication != null)
return false;
}
else if (!effectiveAuthentication.equals(other.effectiveAuthentication))
return false;
if (realAuthentication == null)
{
if (other.realAuthentication != null)
return false;
}
else if (!realAuthentication.equals(other.realAuthentication))
return false;
return true;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
if (realAuthentication == null)
{
builder.append("Real authenticaion = null");
}
else
{
builder.append("Real authenticaion = " + realAuthentication.toString());
}
builder.append(", ");
if (effectiveAuthentication == null)
{
builder.append("Effective authenticaion = null");
}
else
{
builder.append("Effective authenticaion = " + effectiveAuthentication.toString());
}
builder.append(", ");
return builder.toString();
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.security.authentication;
import org.alfresco.error.AlfrescoRuntimeException;
/**
* Alfresco Authentication Exception and wrapper
*
* @author andyh
*
*/
public class AuthenticationException extends AlfrescoRuntimeException
{
/**
*
*/
private static final long serialVersionUID = 3546647620128092466L;
public AuthenticationException(String msg)
{
super(msg);
}
public AuthenticationException(String msg, Throwable cause)
{
super(msg, cause);
}
}

View File

@@ -1,627 +0,0 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.security.authentication;
import java.util.Stack;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.context.Context;
import net.sf.acegisecurity.context.ContextHolder;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.providers.dao.User;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.log.NDC;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
/**
* Utility helper methods to change the authenticated context for threads.
*/
public class AuthenticationUtil implements InitializingBean
{
static Log s_logger = LogFactory.getLog(AuthenticationUtil.class);
public interface RunAsWork<Result>
{
/**
* Method containing the work to be done in the user transaction.
*
* @return Return the result of the operation
*/
Result doWork() throws Exception;
}
private static boolean initialized = false;
public static final String SYSTEM_USER_NAME = "System";
private static String defaultAdminUserName = PermissionService.ADMINISTRATOR_AUTHORITY;
private static String defaultGuestUserName = PermissionService.GUEST_AUTHORITY;
private static boolean mtEnabled = false;
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception
{
// at this point default admin and guest names have been assigned
initialized = true;
}
public void setDefaultAdminUserName(String defaultAdminUserName)
{
AuthenticationUtil.defaultAdminUserName = defaultAdminUserName;
}
public void setDefaultGuestUserName(String defaultGuestUserName)
{
AuthenticationUtil.defaultGuestUserName = defaultGuestUserName;
}
public static void setMtEnabled(boolean mtEnabled)
{
if (!AuthenticationUtil.mtEnabled)
{
AuthenticationUtil.mtEnabled = mtEnabled;
}
}
public static boolean isMtEnabled()
{
return AuthenticationUtil.mtEnabled;
}
public AuthenticationUtil()
{
super();
}
/**
* Utility method to create an authentication token
*/
private static UsernamePasswordAuthenticationToken getAuthenticationToken(String userName, UserDetails providedDetails)
{
UserDetails ud = null;
if (userName.equals(SYSTEM_USER_NAME))
{
GrantedAuthority[] gas = new GrantedAuthority[1];
gas[0] = new GrantedAuthorityImpl("ROLE_SYSTEM");
ud = new User(SYSTEM_USER_NAME, "", true, true, true, true, gas);
}
else if (userName.equalsIgnoreCase(getGuestUserName()))
{
GrantedAuthority[] gas = new GrantedAuthority[0];
ud = new User(getGuestUserName().toLowerCase(), "", true, true, true, true, gas);
}
else
{
if (providedDetails.getUsername().equals(userName))
{
ud = providedDetails;
}
else
{
throw new AuthenticationException("Provided user details do not match the user name");
}
}
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(ud, "", ud.getAuthorities());
auth.setDetails(ud);
auth.setAuthenticated(true);
return auth;
}
/**
* Default implementation that makes an ACEGI object on the fly
*/
private static UserDetails getDefaultUserDetails(String userName)
{
GrantedAuthority[] gas = new GrantedAuthority[1];
gas[0] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED");
UserDetails ud = new User(userName, "", true, true, true, true, gas);
return ud;
}
/**
* Extract the username from the authentication.
*/
private static String getUserName(Authentication authentication)
{
if (authentication.getPrincipal() instanceof UserDetails)
{
return ((UserDetails) authentication.getPrincipal()).getUsername();
}
else
{
return authentication.getPrincipal().toString();
}
}
/**
* Authenticate as the given user. The user will be authenticated and all operations
* with be run in the context of this user.
*
* @param userName the user name
* @return the authentication token
*/
public static Authentication setFullyAuthenticatedUser(String userName)
{
return setFullyAuthenticatedUser(userName, getDefaultUserDetails(userName));
}
private static Authentication setFullyAuthenticatedUser(String userName, UserDetails providedDetails) throws AuthenticationException
{
if (userName == null)
{
throw new AuthenticationException("Null user name");
}
try
{
UsernamePasswordAuthenticationToken auth = getAuthenticationToken(userName, providedDetails);
return setFullAuthentication(auth);
}
catch (net.sf.acegisecurity.AuthenticationException ae)
{
throw new AuthenticationException(ae.getMessage(), ae);
}
}
/**
* Re-authenticate using a previously-created authentication.
*/
public static Authentication setFullAuthentication(Authentication authentication)
{
if (authentication == null)
{
clearCurrentSecurityContext();
return null;
}
else
{
Context context = ContextHolder.getContext();
AlfrescoSecureContext sc = null;
if ((context == null) || !(context instanceof AlfrescoSecureContext))
{
sc = new AlfrescoSecureContextImpl();
ContextHolder.setContext(sc);
}
else
{
sc = (AlfrescoSecureContext) context;
}
authentication.setAuthenticated(true);
// Sets real and effective
sc.setRealAuthentication(authentication);
sc.setEffectiveAuthentication(authentication);
return authentication;
}
}
/**
* <b>WARN: Advanced usage only.</b><br/>
* Set the system user as the currently running user for authentication purposes.
*
* @return Authentication
*
* @see #setRunAsUser(String)
*/
public static Authentication setRunAsUserSystem()
{
return setRunAsUser(SYSTEM_USER_NAME);
}
/**
* <b>WARN: Advanced usage only.</b><br/>
* Switch to the given user for all authenticated operations. The original, authenticated user
* can still be found using {@link #getAuthenticatedUser()}.
*
* @param userName the user to run as
* @return the new authentication
*/
public static Authentication setRunAsUser(String userName)
{
return setRunAsUser(userName, getDefaultUserDetails(userName));
}
/*package*/ static Authentication setRunAsUser(String userName, UserDetails providedDetails) throws AuthenticationException
{
if (userName == null)
{
throw new AuthenticationException("Null user name");
}
try
{
UsernamePasswordAuthenticationToken auth = getAuthenticationToken(userName, providedDetails);
return setRunAsAuthentication(auth);
}
catch (net.sf.acegisecurity.AuthenticationException ae)
{
throw new AuthenticationException(ae.getMessage(), ae);
}
}
/*package*/ static Authentication setRunAsAuthentication(Authentication authentication)
{
if (authentication == null)
{
clearCurrentSecurityContext();
return null;
}
else
{
Context context = ContextHolder.getContext();
AlfrescoSecureContext sc = null;
if ((context == null) || !(context instanceof AlfrescoSecureContext))
{
sc = new AlfrescoSecureContextImpl();
ContextHolder.setContext(sc);
}
else
{
sc = (AlfrescoSecureContext) context;
}
authentication.setAuthenticated(true);
if (sc.getRealAuthentication() == null)
{
// There is no authentication in action
sc.setRealAuthentication(authentication);
}
sc.setEffectiveAuthentication(authentication);
return authentication;
}
}
/**
* Get the current authentication for application of permissions. This includes
* the any overlay details set by {@link #setRunAsUser(String)}.
*
* @return Authentication Returns the running authentication
* @throws AuthenticationException
*/
public static Authentication getRunAsAuthentication() throws AuthenticationException
{
Context context = ContextHolder.getContext();
if ((context == null) || !(context instanceof AlfrescoSecureContext))
{
return null;
}
return ((AlfrescoSecureContext) context).getEffectiveAuthentication();
}
/**
* <b>WARN: Advanced usage only.</b><br/>
* Get the authentication for that was set by an real authentication.
*
* @return Authentication Returns the real authentication
* @throws AuthenticationException
*/
public static Authentication getFullAuthentication() throws AuthenticationException
{
Context context = ContextHolder.getContext();
if ((context == null) || !(context instanceof AlfrescoSecureContext))
{
return null;
}
return ((AlfrescoSecureContext) context).getRealAuthentication();
}
/**
* Get the user that is currently in effect for purposes of authentication. This includes
* any overlays introduced by {@link #setRunAsUser(String) runAs}.
*
* @return Returns the name of the user
* @throws AuthenticationException
*/
public static String getRunAsUser() throws AuthenticationException
{
Context context = ContextHolder.getContext();
if ((context == null) || !(context instanceof AlfrescoSecureContext))
{
return null;
}
AlfrescoSecureContext ctx = (AlfrescoSecureContext) context;
if (ctx.getEffectiveAuthentication() == null)
{
return null;
}
return getUserName(ctx.getEffectiveAuthentication());
}
public static boolean isRunAsUserTheSystemUser()
{
String runAsUser = getRunAsUser();
if ((runAsUser != null) && isMtEnabled())
{
// get base username
int idx = runAsUser.indexOf(TenantService.SEPARATOR);
if (idx != -1)
{
runAsUser = runAsUser.substring(0, idx);
}
}
return EqualsHelper.nullSafeEquals(runAsUser, AuthenticationUtil.SYSTEM_USER_NAME);
}
/**
* Get the fully authenticated user.
* It returns the name of the user that last authenticated and excludes any overlay authentication set
* by {@link #runAs(org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork, String) runAs}.
*
* @return Returns the name of the authenticated user
* @throws AuthenticationException
*/
public static String getFullyAuthenticatedUser() throws AuthenticationException
{
Context context = ContextHolder.getContext();
if ((context == null) || !(context instanceof AlfrescoSecureContext))
{
return null;
}
AlfrescoSecureContext ctx = (AlfrescoSecureContext) context;
if (ctx.getRealAuthentication() == null)
{
return null;
}
return getUserName(ctx.getRealAuthentication());
}
/**
* Get the name of the system user
*
* @return system user name
*/
public static String getSystemUserName()
{
return SYSTEM_USER_NAME;
}
/**
* Get the name of the default admin user (the admin user created during bootstrap)
*
* @return admin user name
*/
public static String getAdminUserName()
{
if (!initialized)
{
throw new IllegalStateException("AuthenticationUtil not yet initialised; default admin username not available");
}
if (isMtEnabled())
{
String runAsUser = AuthenticationUtil.getRunAsUser();
if (runAsUser != null)
{
String[] parts = splitUserTenant(runAsUser);
if (parts.length == 2)
{
return defaultAdminUserName + TenantService.SEPARATOR + parts[1];
}
}
}
return defaultAdminUserName;
}
/*
* Get the name of admin role
*/
public static String getAdminRoleName()
{
return PermissionService.ADMINISTRATOR_AUTHORITY;
}
/**
* Get the name of the Guest User
*/
public static String getGuestUserName()
{
if (!initialized)
{
throw new IllegalStateException("AuthenticationUtil not yet initialised; default guest username not available");
}
return defaultGuestUserName;
}
/**
* Get the name of the guest role
*/
public static String getGuestRoleName()
{
return PermissionService.GUEST_AUTHORITY;
}
/**
* Remove the current security information
*/
public static void clearCurrentSecurityContext()
{
ContextHolder.setContext(null);
InMemoryTicketComponentImpl.clearCurrentSecurityContext();
NDC.remove();
}
/**
* Execute a unit of work as a given user. The thread's authenticated user will be returned to its normal state
* after the call.
*
* @param runAsWork
* the unit of work to do
* @param uid
* the user ID
* @return Returns the work's return value
*/
public static <R> R runAs(RunAsWork<R> runAsWork, String uid)
{
Authentication originalFullAuthentication = AuthenticationUtil.getFullAuthentication();
Authentication originalRunAsAuthentication = AuthenticationUtil.getRunAsAuthentication();
final R result;
try
{
if (originalFullAuthentication == null)
{
AuthenticationUtil.setFullyAuthenticatedUser(uid);
}
else
{
if ((originalRunAsAuthentication != null) && (isMtEnabled()))
{
String originalRunAsUserName = getUserName(originalRunAsAuthentication);
int idx = originalRunAsUserName.indexOf(TenantService.SEPARATOR);
if ((idx != -1) && (idx < (originalRunAsUserName.length() - 1)))
{
if (uid.equals(AuthenticationUtil.getSystemUserName()))
{
uid = uid + TenantService.SEPARATOR + originalRunAsUserName.substring(idx + 1);
}
}
}
AuthenticationUtil.setRunAsUser(uid);
}
logNDC(uid);
result = runAsWork.doWork();
return result;
}
catch (Throwable exception)
{
// Re-throw the exception
if (exception instanceof RuntimeException)
{
throw (RuntimeException) exception;
}
else
{
throw new RuntimeException("Error during run as.", exception);
}
}
finally
{
if (originalFullAuthentication == null)
{
AuthenticationUtil.clearCurrentSecurityContext();
logNDC(null);
}
else
{
AuthenticationUtil.setFullAuthentication(originalFullAuthentication);
AuthenticationUtil.setRunAsAuthentication(originalRunAsAuthentication);
logNDC(getUserName(originalFullAuthentication));
}
}
}
static class ThreadLocalStack extends ThreadLocal<Stack<Authentication>> {
/* (non-Javadoc)
* @see java.lang.ThreadLocal#initialValue()
*/
@Override
protected Stack<Authentication> initialValue()
{
return new Stack<Authentication>();
}
}
private static ThreadLocal<Stack<Authentication>> threadLocalFullAuthenticationStack = new ThreadLocalStack();
private static ThreadLocal<Stack<Authentication>> threadLocalRunAsAuthenticationStack = new ThreadLocalStack();
/**
* Push the current authentication context onto a threadlocal stack.
*/
public static void pushAuthentication()
{
Authentication originalFullAuthentication = AuthenticationUtil.getFullAuthentication();
Authentication originalRunAsAuthentication = AuthenticationUtil.getRunAsAuthentication();
threadLocalFullAuthenticationStack.get().push(originalFullAuthentication);
threadLocalRunAsAuthenticationStack.get().push(originalRunAsAuthentication);
}
/**
* Pop the authentication context from a threadlocal stack.
*/
public static void popAuthentication()
{
Authentication originalFullAuthentication = threadLocalFullAuthenticationStack.get().pop();
Authentication originalRunAsAuthentication = threadLocalRunAsAuthenticationStack.get().pop();
if (originalFullAuthentication == null)
{
AuthenticationUtil.clearCurrentSecurityContext();
}
else
{
AuthenticationUtil.setFullAuthentication(originalFullAuthentication);
AuthenticationUtil.setRunAsAuthentication(originalRunAsAuthentication);
}
}
/**
* Logs the current authenticated users
*/
public static void logAuthenticatedUsers()
{
if (s_logger.isDebugEnabled())
{
s_logger.debug(
"Authentication: \n" +
" Fully authenticated: " + AuthenticationUtil.getFullyAuthenticatedUser() + "\n" +
" Run as: " + AuthenticationUtil.getRunAsUser());
}
}
public static void logNDC(String userName)
{
NDC.remove();
if (userName != null)
{
if (isMtEnabled())
{
String[] parts = splitUserTenant(userName);
if (parts.length == 2)
{
NDC.push("Tenant:" + parts[1] + " User:" + parts[0]);
}
else
{
NDC.push("User:" + userName);
}
}
else
{
NDC.push("User:" + userName);
}
}
}
private static String[] splitUserTenant(String userName)
{
return userName.split(TenantService.SEPARATOR);
}
}

View File

@@ -1,598 +0,0 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.security.authentication;
import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.CRC32;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.service.cmr.repository.datatype.Duration;
import org.alfresco.util.GUID;
import org.apache.commons.codec.binary.Hex;
import org.safehaus.uuid.UUIDGenerator;
/**
* Store tickets in memory. They can be distributed in a cluster via the cache
*
* @author andyh
*/
public class InMemoryTicketComponentImpl implements TicketComponent
{
/**
* Ticket prefix
*/
public static final String GRANTED_AUTHORITY_TICKET_PREFIX = "TICKET_";
private static ThreadLocal<String> currentTicket = new ThreadLocal<String>();
private boolean ticketsExpire;
private Duration validDuration;
private boolean oneOff;
private String guid;
private SimpleCache<String, Ticket> ticketsCache; // Can't use Ticket as it's private
private ExpiryMode expiryMode = ExpiryMode.AFTER_FIXED_TIME;
/**
* IOC constructor
*/
public InMemoryTicketComponentImpl()
{
super();
guid = GUID.generate();
}
/**
* Set the ticket cache to support clustering
*
* @param ticketsCache
*/
public void setTicketsCache(SimpleCache<String, Ticket> ticketsCache)
{
this.ticketsCache = ticketsCache;
}
public String getNewTicket(String userName, String sessionId) throws AuthenticationException
{
Date expiryDate = null;
if (ticketsExpire)
{
expiryDate = Duration.add(new Date(), validDuration);
}
Ticket ticket = new Ticket(ticketsExpire ? expiryMode : ExpiryMode.DO_NOT_EXPIRE, expiryDate, userName,
validDuration, sessionId == null ? Collections.<String> emptySet() : Collections.singleton(sessionId));
ticketsCache.put(ticket.getTicketId(), ticket);
String ticketString = GRANTED_AUTHORITY_TICKET_PREFIX + ticket.getTicketId();
currentTicket.set(ticketString);
return ticketString;
}
public String validateTicket(String ticketString, String sessionId) throws AuthenticationException
{
String ticketKey = getTicketKey(ticketString);
Ticket ticket = this.ticketsCache.get(ticketKey);
if (ticket == null)
{
throw new AuthenticationException("Missing ticket for " + ticketString);
}
if (ticket.hasExpired())
{
throw new TicketExpiredException("Ticket expired for " + ticketString);
}
// TODO: Recheck the user details here
// TODO: Strengthen ticket as GUID is predicatble
if (oneOff)
{
ticketsCache.remove(ticketKey);
}
// Make sure the association with the session is recorded
else if (sessionId != null)
{
Ticket newTicket = ticket.addSessionId(sessionId);
if (newTicket != ticket)
{
ticketsCache.put(ticketKey, newTicket);
ticket = newTicket;
}
}
currentTicket.set(ticketString);
return ticket.getUserName();
}
/**
* Helper method to find a ticket
*
* @param ticketString
* @return - the ticket
*/
private Ticket getTicketByTicketString(String ticketString)
{
Ticket ticket = ticketsCache.get(getTicketKey(ticketString));
return ticket;
}
/**
* Helper method to extract the ticket id from the ticket string
*
* @param ticketString
* @return - the ticket key
*/
private String getTicketKey(String ticketString)
{
if (ticketString == null)
{
return null;
}
else if (ticketString.length() < GRANTED_AUTHORITY_TICKET_PREFIX.length())
{
throw new AuthenticationException(ticketString + " is an invalid ticket format");
}
String key = ticketString.substring(GRANTED_AUTHORITY_TICKET_PREFIX.length());
return key;
}
public void invalidateTicketById(String ticketString, String sessionId)
{
String ticketKey = getTicketKey(ticketString);
// If we are dissassociating the ticket from an app server session, it may still not be time to expire it, as it
// may be in use by other sessions
if (sessionId != null)
{
Ticket ticketObj = ticketsCache.get(ticketKey);
if (ticketObj != null)
{
ticketObj = ticketObj.removeSessionId(sessionId);
if (ticketObj == null)
{
ticketsCache.remove(ticketKey);
}
else
{
ticketsCache.put(ticketKey, ticketObj);
}
}
}
else
{
ticketsCache.remove(ticketKey);
}
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.security.authentication.TicketComponent#getUsersWithTickets(boolean)
*/
public Set<String> getUsersWithTickets(boolean nonExpiredOnly)
{
Set<String> users = new HashSet<String>();
for (String key : ticketsCache.getKeys())
{
Ticket ticket = ticketsCache.get(key);
if (ticket != null)
{
if ((nonExpiredOnly == false) || (! ticket.hasExpired()))
{
users.add(ticket.getUserName());
}
}
}
return users;
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.security.authentication.TicketComponent#countTickets(boolean)
*/
public int countTickets(boolean nonExpiredOnly)
{
if (nonExpiredOnly)
{
int count = 0;
for (String key : ticketsCache.getKeys())
{
Ticket ticket = ticketsCache.get(key);
if (! ticket.hasExpired())
{
count++;
}
}
return count;
}
else
{
return ticketsCache.getKeys().size();
}
}
/*
* (non-Javadoc)
* @see org.alfresco.repo.security.authentication.TicketComponent#invalidateTickets(boolean)
*/
public int invalidateTickets(boolean expiredOnly)
{
int count = 0;
if (! expiredOnly)
{
count = ticketsCache.getKeys().size();
ticketsCache.clear();
}
else
{
for (String key : ticketsCache.getKeys())
{
Ticket ticket = ticketsCache.get(key);
if (ticket == null || ticket.hasExpired())
{
count++;
ticketsCache.remove(key);
}
}
}
return count;
}
public void invalidateTicketByUser(String userName)
{
Set<String> toRemove = new HashSet<String>();
for (String key : ticketsCache.getKeys())
{
Ticket ticket = ticketsCache.get(key);
// Hack: The getKeys() call might return keys for null marker objects, yielding null values
if (ticket == null)
{
continue;
}
if (ticket.getUserName().equals(userName))
{
toRemove.add(ticket.getTicketId());
}
}
for (String id : toRemove)
{
ticketsCache.remove(id);
}
}
@Override
public int hashCode()
{
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((guid == null) ? 0 : guid.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final InMemoryTicketComponentImpl other = (InMemoryTicketComponentImpl) obj;
if (guid == null)
{
if (other.guid != null)
return false;
}
else if (!guid.equals(other.guid))
return false;
return true;
}
/**
* Ticket
*
* @author andyh
*/
public static class Ticket implements Serializable
{
private static final long serialVersionUID = -5904510560161261049L;
private ExpiryMode expires;
private Date expiryDate;
private String userName;
private String ticketId;
private String guid;
private Duration validDuration;
private Set<String> sessionIds;
private Ticket(Ticket copy, Set<String> sessionIds)
{
this.expires = copy.expires;
this.expiryDate = copy.expiryDate;
this.userName = copy.userName;
this.validDuration = copy.validDuration;
this.guid = copy.guid;
this.ticketId = copy.ticketId;
this.sessionIds = sessionIds;
}
Ticket(ExpiryMode expires, Date expiryDate, String userName, Duration validDuration, Set<String> sessionIds)
{
this.expires = expires;
this.expiryDate = expiryDate;
this.userName = userName;
this.validDuration = validDuration;
this.sessionIds = sessionIds;
this.guid = UUIDGenerator.getInstance().generateRandomBasedUUID().toString();
String encode = (expires.toString()) + ((expiryDate == null) ? new Date().toString() : expiryDate.toString()) + userName + guid;
MessageDigest digester;
try
{
digester = MessageDigest.getInstance("SHA-1");
this.ticketId = new String(Hex.encodeHex(digester.digest(encode.getBytes())));
}
catch (NoSuchAlgorithmException e)
{
try
{
digester = MessageDigest.getInstance("MD5");
this.ticketId = new String(Hex.encodeHex(digester.digest(encode.getBytes())));
}
catch (NoSuchAlgorithmException e1)
{
CRC32 crc = new CRC32();
crc.update(encode.getBytes());
byte[] bytes = new byte[4];
long value = crc.getValue();
bytes[0] = (byte) (value & 0xFF);
value >>>= 4;
bytes[1] = (byte) (value & 0xFF);
value >>>= 4;
bytes[2] = (byte) (value & 0xFF);
value >>>= 4;
bytes[3] = (byte) (value & 0xFF);
this.ticketId = new String(Hex.encodeHex(bytes));
}
}
}
public Ticket addSessionId(String sessionId)
{
if (this.sessionIds.contains(sessionId))
{
return this;
}
Set<String> newSessionIds = new HashSet<String>(this.sessionIds.size() * 2 + 2);
newSessionIds.addAll(this.sessionIds);
newSessionIds.add(sessionId);
return new Ticket(this, newSessionIds);
}
public Ticket removeSessionId(String sessionId)
{
if (this.sessionIds.contains(sessionId))
{
Set<String> newSessionIds;
if (this.sessionIds.size() > 1)
{
newSessionIds = new HashSet<String>(this.sessionIds.size() * 2 - 2);
newSessionIds.addAll(this.sessionIds);
newSessionIds.remove(sessionId);
return new Ticket(this, newSessionIds);
}
return null;
}
return this;
}
/**
* Has the ticket expired
*
* @return - if expired
*/
boolean hasExpired()
{
switch (expires)
{
case AFTER_FIXED_TIME:
if ((expiryDate != null) && (expiryDate.compareTo(new Date()) < 0))
{
return true;
}
else
{
return false;
}
case AFTER_INACTIVITY:
Date now = new Date();
if ((expiryDate != null) && (expiryDate.compareTo(now) < 0))
{
return true;
}
else
{
expiryDate = Duration.add(now, validDuration);
return false;
}
case DO_NOT_EXPIRE:
default:
return false;
}
}
public boolean equals(Object o)
{
if (o == this)
{
return true;
}
if (!(o instanceof Ticket))
{
return false;
}
Ticket t = (Ticket) o;
return (this.expires == t.expires) && this.expiryDate.equals(t.expiryDate) && this.userName.equals(t.userName) && this.ticketId.equals(t.ticketId);
}
public int hashCode()
{
return ticketId.hashCode();
}
protected ExpiryMode getExpires()
{
return expires;
}
protected Date getExpiryDate()
{
return expiryDate;
}
protected String getTicketId()
{
return ticketId;
}
protected String getUserName()
{
return userName;
}
protected Set<String> getSessionIds()
{
return sessionIds;
}
}
/**
* Are tickets single use
*
* @param oneOff
*/
public void setOneOff(boolean oneOff)
{
this.oneOff = oneOff;
}
/**
* Do tickets expire
*
* @param ticketsExpire
*/
public void setTicketsExpire(boolean ticketsExpire)
{
this.ticketsExpire = ticketsExpire;
}
/**
* How should tickets expire.
* @param exipryMode
*/
public void setExpiryMode(String expiryMode)
{
this.expiryMode = ExpiryMode.valueOf(expiryMode);
}
/**
* How long are tickets valid (XML duration as a string)
*
* @param validDuration
*/
public void setValidDuration(String validDuration)
{
this.validDuration = new Duration(validDuration);
}
public String getAuthorityForTicket(String ticketString)
{
Ticket ticket = getTicketByTicketString(ticketString);
if (ticket == null)
{
return null;
}
return ticket.getUserName();
}
public String getCurrentTicket(String userName, String sessionId, boolean autoCreate)
{
String ticketString = currentTicket.get();
if (ticketString == null)
{
return autoCreate ? getNewTicket(userName, sessionId) : null;
}
String ticketKey = getTicketKey(ticketString);
Ticket ticketObj = this.ticketsCache.get(ticketKey);
if (ticketObj != null && userName.equals(ticketObj.getUserName()))
{
if (sessionId != null)
{
// A current, as yet unclaimed valid ticket. Make the association with the session now
if (ticketObj.getSessionIds().isEmpty())
{
this.ticketsCache.put(ticketKey, ticketObj.addSessionId(sessionId));
}
// The ticket is already claimed by at least one other session, so start a new one
else
{
return autoCreate ? getNewTicket(userName, sessionId) : null;
}
}
return ticketString;
}
else
{
return autoCreate ? getNewTicket(userName, sessionId) : null;
}
}
public void clearCurrentTicket()
{
clearCurrentSecurityContext();
}
public static void clearCurrentSecurityContext()
{
currentTicket.set(null);
}
public enum ExpiryMode
{
AFTER_INACTIVITY, AFTER_FIXED_TIME, DO_NOT_EXPIRE;
}
}

View File

@@ -1,144 +0,0 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.security.authentication;
import java.util.Set;
/**
* Manage authentication tickets
*
* @author andyh
*
*/
public interface TicketComponent
{
/**
* Register a new ticket. If a session ID is given, an association with that session ID will be recorded for the
* returned ticket.
*
* @param userName
* @param sessionId
* the app server session ID (e.g. HttpSession ID) or <code>null</code> if not applicable.
* @return - the ticket
* @throws AuthenticationException
*/
public String getNewTicket(String userName, String sessionId) throws AuthenticationException;
/**
* Gets the current ticket. If a session ID is given, an association with that session ID will be recorded for the
* returned ticket.
*
* @param userName
* @param sessionId
* the app server session ID (e.g. HttpSession ID) or <code>null</code> if not applicable.
* @param autoCreate
* should we create one automatically if there isn't one?
* @return - the ticket
*/
public String getCurrentTicket(String userName, String sessionId, boolean autoCreate);
/**
* Check that a certificate is valid and can be used in place of a login. Optionally records an association between
* the ticket and a given app server session Id. This is so that we don't expire tickets prematurely referenced by
* more than one application server session. Tickets may be rejected because:
* <ol>
* <li>The certificate does not exists
* <li>The status of the user has changed
* <ol>
* <li>The user is locked
* <li>The account has expired
* <li>The credentials have expired
* <li>The account is disabled
* </ol>
* <li>The ticket may have expired
* <ol>
* <li>The ticked my be invalid by timed expiry
* <li>An attemp to reuse a once only ticket
* </ol>
* </ol>
*
* @param ticket
* @param sessionId
* the app server session ID (e.g. HttpSession ID) or <code>null</code> if not applicable.
* @return - the user name
* @throws AuthenticationException
*/
public String validateTicket(String ticket, String sessionId) throws AuthenticationException;
/**
* Invalidates a ticket, or disassociates it from an app server session. Once it has been disassociated from all
* sessions, the ticket will be invalidated globally.
*
* @param ticket
* @param sessionId
* the app server session ID (e.g. HttpSession ID) or <code>null</code> if the ticket should be
* invalidated globally.
*/
public void invalidateTicketById(String ticket, String sessionId);
/**
* Invalidate all user tickets
*
* @param userName
*/
public void invalidateTicketByUser(String userName);
/**
* Count tickets
*
* This may be higher than the user count, since a user can have more than one ticket/session
*
* @param nonExpiredOnly true for non expired tickets, false for all (including expired) tickets
* @return int number of tickets
*/
public int countTickets(boolean nonExpiredOnly);
/**
* Get set of users with tickets
*
* This may be lower than the ticket count, since a user can have more than one ticket/session
*
* @param nonExpiredOnly true for non expired tickets, false for all (including expired) tickets
* @return Set<String> set of users with (one or more) tickets
*/
public Set<String> getUsersWithTickets(boolean nonExpiredOnly);
/**
* Invalidate tickets
*
* @param expiredOnly true for EXPIRED tickets, false for ALL (including non-expired) tickets
* @return int count of invalidated tickets
*/
public int invalidateTickets(boolean expiredOnly);
/**
* Get the authority for the given ticket
*
* @param ticket
* @return the authority
*/
public String getAuthorityForTicket(String ticket);
/**
* Clear the current ticket
*
*/
public void clearCurrentTicket();
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.security.authentication;
public class TicketExpiredException extends AuthenticationException
{
/**
*
*/
private static final long serialVersionUID = 3257572801815590969L;
public TicketExpiredException(String msg)
{
super(msg);
}
public TicketExpiredException(String msg, Throwable cause)
{
super(msg, cause);
}
}