Improvements to the ticket service

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5984 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Andrew Hind 2007-06-15 16:05:43 +00:00
parent 1544ecb1b6
commit 299b4d3434
7 changed files with 263 additions and 54 deletions

View File

@ -153,9 +153,14 @@ public class AuthenticationServiceImpl implements AuthenticationService
public String getCurrentTicket() public String getCurrentTicket()
{ {
return ticketComponent.getTicket(getCurrentUserName()); return ticketComponent.getCurrentTicket(getCurrentUserName());
} }
public String getNewTicket()
{
return ticketComponent.getNewTicket(getCurrentUserName());
}
public void clearCurrentSecurityContext() public void clearCurrentSecurityContext()
{ {
authenticationComponent.clearCurrentSecurityContext(); authenticationComponent.clearCurrentSecurityContext();

View File

@ -453,8 +453,9 @@ public class AuthenticationTest extends TestCase
Authentication result = authenticationManager.authenticate(token); Authentication result = authenticationManager.authenticate(token);
result.setAuthenticated(true); result.setAuthenticated(true);
String ticket = ticketComponent.getTicket(getUserName(result)); String ticket = ticketComponent.getNewTicket(getUserName(result));
String user = ticketComponent.validateTicket(ticket); String user = ticketComponent.validateTicket(ticket);
assertEquals(ticketComponent.getCurrentTicket("Andy"), ticket);
user = null; user = null;
try try
@ -471,6 +472,7 @@ public class AuthenticationTest extends TestCase
try try
{ {
user = ticketComponent.validateTicket(ticket); user = ticketComponent.validateTicket(ticket);
assertEquals(ticketComponent.getCurrentTicket("Andy"), ticket);
assertNotNull(null); assertNotNull(null);
} }
catch (AuthenticationException e) catch (AuthenticationException e)
@ -499,9 +501,11 @@ public class AuthenticationTest extends TestCase
Authentication result = authenticationManager.authenticate(token); Authentication result = authenticationManager.authenticate(token);
result.setAuthenticated(true); result.setAuthenticated(true);
String ticket = tc.getTicket(getUserName(result)); String ticket = tc.getNewTicket(getUserName(result));
tc.validateTicket(ticket); tc.validateTicket(ticket);
assertEquals(ticketComponent.getCurrentTicket("Andy"), ticket);
tc.validateTicket(ticket); tc.validateTicket(ticket);
assertEquals(ticketComponent.getCurrentTicket("Andy"), ticket);
dao.deleteUser("Andy"); dao.deleteUser("Andy");
// assertNull(dao.getUserOrNull("Andy")); // assertNull(dao.getUserOrNull("Andy"));
@ -523,8 +527,9 @@ public class AuthenticationTest extends TestCase
Authentication result = authenticationManager.authenticate(token); Authentication result = authenticationManager.authenticate(token);
result.setAuthenticated(true); result.setAuthenticated(true);
String ticket = tc.getTicket(getUserName(result)); String ticket = tc.getNewTicket(getUserName(result));
tc.validateTicket(ticket); tc.validateTicket(ticket);
assertTrue(!ticketComponent.getCurrentTicket("Andy").equals(ticket));
try try
{ {
tc.validateTicket(ticket); tc.validateTicket(ticket);
@ -555,10 +560,13 @@ public class AuthenticationTest extends TestCase
Authentication result = authenticationManager.authenticate(token); Authentication result = authenticationManager.authenticate(token);
result.setAuthenticated(true); result.setAuthenticated(true);
String ticket = tc.getTicket(getUserName(result)); String ticket = tc.getNewTicket(getUserName(result));
tc.validateTicket(ticket); tc.validateTicket(ticket);
assertEquals(ticketComponent.getCurrentTicket("Andy"), ticket);
tc.validateTicket(ticket); tc.validateTicket(ticket);
assertEquals(ticketComponent.getCurrentTicket("Andy"), ticket);
tc.validateTicket(ticket); tc.validateTicket(ticket);
assertEquals(ticketComponent.getCurrentTicket("Andy"), ticket);
synchronized (this) synchronized (this)
{ {
@ -645,10 +653,13 @@ public class AuthenticationTest extends TestCase
Authentication result = authenticationManager.authenticate(token); Authentication result = authenticationManager.authenticate(token);
result.setAuthenticated(true); result.setAuthenticated(true);
String ticket = tc.getTicket(getUserName(result)); String ticket = tc.getNewTicket(getUserName(result));
tc.validateTicket(ticket); tc.validateTicket(ticket);
assertEquals(ticketComponent.getCurrentTicket("Andy"), ticket);
tc.validateTicket(ticket); tc.validateTicket(ticket);
assertEquals(ticketComponent.getCurrentTicket("Andy"), ticket);
tc.validateTicket(ticket); tc.validateTicket(ticket);
assertEquals(ticketComponent.getCurrentTicket("Andy"), ticket);
synchronized (this) synchronized (this)
{ {
try try
@ -663,7 +674,8 @@ public class AuthenticationTest extends TestCase
} }
tc.validateTicket(ticket); tc.validateTicket(ticket);
assertEquals(ticketComponent.getCurrentTicket("Andy"), ticket);
dao.deleteUser("Andy"); dao.deleteUser("Andy");
// assertNull(dao.getUserOrNull("Andy")); // assertNull(dao.getUserOrNull("Andy"));
@ -772,6 +784,7 @@ public class AuthenticationTest extends TestCase
String ticket = authenticationService.getCurrentTicket(); String ticket = authenticationService.getCurrentTicket();
// validate our ticket is still valid // validate our ticket is still valid
authenticationService.validate(ticket); authenticationService.validate(ticket);
assertEquals(ticket, authenticationService.getCurrentTicket());
// destroy the ticket instance // destroy the ticket instance
authenticationService.invalidateTicket(ticket); authenticationService.invalidateTicket(ticket);
@ -820,6 +833,7 @@ public class AuthenticationTest extends TestCase
authenticationService.clearCurrentSecurityContext(); authenticationService.clearCurrentSecurityContext();
authenticationService.validate(ticket); authenticationService.validate(ticket);
assertEquals(ticket, authenticationService.getCurrentTicket());
// destroy the ticket instance // destroy the ticket instance
authenticationService.invalidateTicket(ticket); authenticationService.invalidateTicket(ticket);
@ -878,6 +892,7 @@ public class AuthenticationTest extends TestCase
String ticket = authenticationService.getCurrentTicket(); String ticket = authenticationService.getCurrentTicket();
// validate our ticket is still valid // validate our ticket is still valid
authenticationService.validate(ticket); authenticationService.validate(ticket);
assertEquals(ticket, authenticationService.getCurrentTicket());
// destroy the ticket instance // destroy the ticket instance
authenticationService.invalidateTicket(ticket); authenticationService.invalidateTicket(ticket);
@ -937,6 +952,7 @@ public class AuthenticationTest extends TestCase
String ticket = authenticationService.getCurrentTicket(); String ticket = authenticationService.getCurrentTicket();
// validate our ticket is still valid // validate our ticket is still valid
authenticationService.validate(ticket); authenticationService.validate(ticket);
assertEquals(ticket, authenticationService.getCurrentTicket());
// destroy the ticket instance // destroy the ticket instance
authenticationService.invalidateTicket(ticket); authenticationService.invalidateTicket(ticket);
@ -1098,6 +1114,7 @@ public class AuthenticationTest extends TestCase
// validate our ticket is still valid // validate our ticket is still valid
pubAuthenticationService.validate(ticket); pubAuthenticationService.validate(ticket);
assertEquals(ticket, authenticationService.getCurrentTicket());
// destroy the ticket instance // destroy the ticket instance
pubAuthenticationService.invalidateTicket(ticket); pubAuthenticationService.invalidateTicket(ticket);
@ -1166,6 +1183,7 @@ public class AuthenticationTest extends TestCase
String ticket = pubAuthenticationService.getCurrentTicket(); String ticket = pubAuthenticationService.getCurrentTicket();
// validate our ticket is still valid // validate our ticket is still valid
pubAuthenticationService.validate(ticket); pubAuthenticationService.validate(ticket);
assertEquals(ticket, authenticationService.getCurrentTicket());
// destroy the ticket instance // destroy the ticket instance
pubAuthenticationService.invalidateTicket(ticket); pubAuthenticationService.invalidateTicket(ticket);
@ -1226,6 +1244,7 @@ public class AuthenticationTest extends TestCase
String ticket = pubAuthenticationService.getCurrentTicket(); String ticket = pubAuthenticationService.getCurrentTicket();
// validate our ticket is still valid // validate our ticket is still valid
pubAuthenticationService.validate(ticket); pubAuthenticationService.validate(ticket);
assertEquals(ticket, authenticationService.getCurrentTicket());
// destroy the ticket instance // destroy the ticket instance
pubAuthenticationService.invalidateTicket(ticket); pubAuthenticationService.invalidateTicket(ticket);

View File

@ -301,6 +301,22 @@ public class ChainingAuthenticationServiceImpl implements AuthenticationService
} }
return null; return null;
} }
public String getNewTicket()
{
for (AuthenticationService authService : getUsableAuthenticationServices())
{
try
{
return authService.getNewTicket();
}
catch (AuthenticationException e)
{
// Ignore and chain
}
}
return null;
}
public void clearCurrentSecurityContext() public void clearCurrentSecurityContext()
{ {

View File

@ -25,57 +25,76 @@
package org.alfresco.repo.security.authentication; package org.alfresco.repo.security.authentication;
import java.io.Serializable; import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.zip.CRC32;
import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.service.cmr.repository.datatype.Duration; import org.alfresco.service.cmr.repository.datatype.Duration;
import org.alfresco.util.GUID; import org.apache.commons.codec.binary.Hex;
import org.doomdark.uuid.UUIDGenerator;
/**
* Store tickets in memory. They can be distributed in a cluster via the cache
*
* @author andyh
*/
public class InMemoryTicketComponentImpl implements TicketComponent public class InMemoryTicketComponentImpl implements TicketComponent
{ {
/**
* Ticket prefix
*/
public static final String GRANTED_AUTHORITY_TICKET_PREFIX = "TICKET_"; public static final String GRANTED_AUTHORITY_TICKET_PREFIX = "TICKET_";
private static ThreadLocal<String> currentTicket = new ThreadLocal<String>();
private boolean ticketsExpire; private boolean ticketsExpire;
private Duration validDuration; private Duration validDuration;
private boolean oneOff; private boolean oneOff;
private SimpleCache<String, Ticket> ticketsCache; // Can't use Ticket as it's private private SimpleCache<String, Ticket> ticketsCache; // Can't use Ticket as it's private
/**
* IOC constructor
*
*/
public InMemoryTicketComponentImpl() public InMemoryTicketComponentImpl()
{ {
super(); super();
} }
/**
* Set the ticket cache to support clustering
*
* @param ticketsCache
*/
public void setTicketsCache(SimpleCache<String, Ticket> ticketsCache) public void setTicketsCache(SimpleCache<String, Ticket> ticketsCache)
{ {
this.ticketsCache = ticketsCache; this.ticketsCache = ticketsCache;
} }
public String getTicket(String userName) throws AuthenticationException public String getNewTicket(String userName) throws AuthenticationException
{ {
Date expiryDate = null; Date expiryDate = null;
if (ticketsExpire) if (ticketsExpire)
{ {
expiryDate = Duration.add(new Date(), validDuration); expiryDate = Duration.add(new Date(), validDuration);
} }
Ticket ticket = new Ticket(ticketsExpire, expiryDate, userName); Ticket ticket = new Ticket(ticketsExpire, expiryDate, userName);
ticketsCache.put(ticket.getTicketId(), ticket); ticketsCache.put(ticket.getTicketId(), ticket);
String ticketString = GRANTED_AUTHORITY_TICKET_PREFIX + ticket.getTicketId();
return GRANTED_AUTHORITY_TICKET_PREFIX + ticket.getTicketId(); currentTicket.set(ticketString);
return ticketString;
} }
public String validateTicket(String ticketString) throws AuthenticationException public String validateTicket(String ticketString) throws AuthenticationException
{ {
if (ticketString.length() < GRANTED_AUTHORITY_TICKET_PREFIX.length()) Ticket ticket = getTicketByTicketString(ticketString);
{
throw new AuthenticationException(ticketString + " is an invalid ticket format");
}
String key = ticketString.substring(GRANTED_AUTHORITY_TICKET_PREFIX.length());
Ticket ticket = ticketsCache.get(key);
if (ticket == null) if (ticket == null)
{ {
throw new AuthenticationException("Missing ticket for " + ticketString); throw new AuthenticationException("Missing ticket for " + ticketString);
@ -86,40 +105,70 @@ public class InMemoryTicketComponentImpl implements TicketComponent
} }
// TODO: Recheck the user details here // TODO: Recheck the user details here
// TODO: Strengthen ticket as GUID is predicatble // TODO: Strengthen ticket as GUID is predicatble
if(oneOff) if (oneOff)
{ {
ticketsCache.remove(key); ticketsCache.remove(getTicketKey(ticketString));
} }
currentTicket.set(ticketString);
return ticket.getUserName(); 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.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) public void invalidateTicketById(String ticketString)
{ {
String key = ticketString.substring(GRANTED_AUTHORITY_TICKET_PREFIX.length()); String key = ticketString.substring(GRANTED_AUTHORITY_TICKET_PREFIX.length());
ticketsCache.remove(key); ticketsCache.remove(key);
} }
public void invalidateTicketByUser(String userName) public void invalidateTicketByUser(String userName)
{ {
Set<String> toRemove = new HashSet<String>(); Set<String> toRemove = new HashSet<String>();
for(String key: ticketsCache.getKeys()) for (String key : ticketsCache.getKeys())
{ {
Ticket ticket = ticketsCache.get(key); Ticket ticket = ticketsCache.get(key);
if(ticket.getUserName().equals(userName)) if (ticket.getUserName().equals(userName))
{ {
toRemove.add(ticket.getTicketId()); toRemove.add(ticket.getTicketId());
} }
} }
for(String id: toRemove) for (String id : toRemove)
{ {
ticketsCache.remove(id); ticketsCache.remove(id);
} }
} }
/**
* Ticket
* @author andyh
*
*/
public static class Ticket implements Serializable public static class Ticket implements Serializable
{ {
private static final long serialVersionUID = -5904510560161261049L; private static final long serialVersionUID = -5904510560161261049L;
@ -132,18 +181,54 @@ public class InMemoryTicketComponentImpl implements TicketComponent
private String ticketId; private String ticketId;
private String guid;
Ticket(boolean expires, Date expiryDate, String userName) Ticket(boolean expires, Date expiryDate, String userName)
{ {
this.expires = expires; this.expires = expires;
this.expiryDate = expiryDate; this.expiryDate = expiryDate;
this.userName = userName; this.userName = userName;
this.ticketId = GUID.generate(); this.guid = UUIDGenerator.getInstance().generateRandomBasedUUID().toString();
String encode = (expires ? "T" : "F") +
((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));
}
}
} }
/** /**
* Has the tick expired * Has the tick expired
* *
* @return * @return - if expired
*/ */
boolean hasExpired() boolean hasExpired()
{ {
@ -168,7 +253,9 @@ public class InMemoryTicketComponentImpl implements TicketComponent
return false; return false;
} }
Ticket t = (Ticket) o; Ticket t = (Ticket) o;
return (this.expires == t.expires) && this.expiryDate.equals(t.expiryDate) && this.userName.equals(t.userName) && this.ticketId.equals(t.ticketId); return (this.expires == t.expires)
&& this.expiryDate.equals(t.expiryDate) && this.userName.equals(t.userName)
&& this.ticketId.equals(t.ticketId);
} }
public int hashCode() public int hashCode()
@ -198,23 +285,59 @@ public class InMemoryTicketComponentImpl implements TicketComponent
} }
/**
* Are tickets single use
* @param oneOff
*/
public void setOneOff(boolean oneOff) public void setOneOff(boolean oneOff)
{ {
this.oneOff = oneOff; this.oneOff = oneOff;
} }
/**
* Do tickets expire
* @param ticketsExpire
*/
public void setTicketsExpire(boolean ticketsExpire) public void setTicketsExpire(boolean ticketsExpire)
{ {
this.ticketsExpire = ticketsExpire; this.ticketsExpire = ticketsExpire;
} }
/**
* How long are tickets valid (XML duration as a string)
* @param validDuration
*/
public void setValidDuration(String validDuration) public void setValidDuration(String validDuration)
{ {
this.validDuration = new Duration(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 ticket = currentTicket.get();
if(ticket == null)
{
return getNewTicket(userName);
}
String ticketUser = getAuthorityForTicket(ticket);
if(userName.equals(ticketUser))
{
return ticket;
}
else
{
return getNewTicket(userName);
}
}
} }

View File

@ -315,6 +315,7 @@ public class TestAuthenticationServiceImpl implements AuthenticationService
} }
// TODO: Fix this up
public String getCurrentTicket() public String getCurrentTicket()
{ {
String currentUser = getCurrentUserName(); String currentUser = getCurrentUserName();
@ -326,6 +327,18 @@ public class TestAuthenticationServiceImpl implements AuthenticationService
} }
return ticket; return ticket;
} }
public String getNewTicket()
{
String currentUser = getCurrentUserName();
String ticket = userToTicket.get(currentUser);
if (ticket == null)
{
ticket = GUID.generate();
userToTicket.put(currentUser, ticket);
}
return ticket;
}
public void clearCurrentSecurityContext() public void clearCurrentSecurityContext()
{ {

View File

@ -34,14 +34,23 @@ package org.alfresco.repo.security.authentication;
public interface TicketComponent public interface TicketComponent
{ {
/** /**
* Register a ticket * Register a new ticket
* *
* @param authentication * @param userName
* @return * @return - the ticket
* @throws AuthenticationException * @throws AuthenticationException
*/ */
public String getTicket(String userName) throws AuthenticationException; public String getNewTicket(String userName) throws AuthenticationException;
/**
* Get the current ticket
*
* @param userName
* @return - the ticket
*/
public String getCurrentTicket(String userName);
/** /**
* Check that a certificate is valid and can be used in place of a login. * Check that a certificate is valid and can be used in place of a login.
* *
@ -62,13 +71,30 @@ public interface TicketComponent
* </ol> * </ol>
* </ol> * </ol>
* *
* @param authentication * @param ticket
* @return * @return - the user name
* @throws AuthenticationException * @throws AuthenticationException
*/ */
public String validateTicket(String ticket) throws AuthenticationException; public String validateTicket(String ticket) throws AuthenticationException;
/**
* Invalidate the tickets by id
* @param ticket
*/
public void invalidateTicketById(String ticket); public void invalidateTicketById(String ticket);
/**
* Invalidate all user tickets
*
* @param userName
*/
public void invalidateTicketByUser(String userName); public void invalidateTicketByUser(String userName);
/**
* Get the authority for the given ticket
*
* @param ticket
* @return the authority
*/
public String getAuthorityForTicket(String ticket);
} }

View File

@ -178,6 +178,13 @@ public interface AuthenticationService
@Auditable @Auditable
public String getCurrentTicket(); public String getCurrentTicket();
/**
* Get the current ticket as a string
* @return
*/
@Auditable
public String getNewTicket();
/** /**
* Remove the current security information * Remove the current security information
* *