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()
{
return ticketComponent.getTicket(getCurrentUserName());
return ticketComponent.getCurrentTicket(getCurrentUserName());
}
public String getNewTicket()
{
return ticketComponent.getNewTicket(getCurrentUserName());
}
public void clearCurrentSecurityContext()
{
authenticationComponent.clearCurrentSecurityContext();

View File

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

View File

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

View File

@ -25,57 +25,76 @@
package org.alfresco.repo.security.authentication;
import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
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.doomdark.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 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()
{
super();
}
/**
* Set the ticket cache to support clustering
*
* @param 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;
if (ticketsExpire)
{
expiryDate = Duration.add(new Date(), validDuration);
}
Ticket ticket = new Ticket(ticketsExpire, expiryDate, userName);
ticketsCache.put(ticket.getTicketId(), ticket);
return GRANTED_AUTHORITY_TICKET_PREFIX + ticket.getTicketId();
Date expiryDate = null;
if (ticketsExpire)
{
expiryDate = Duration.add(new Date(), validDuration);
}
Ticket ticket = new Ticket(ticketsExpire, expiryDate, userName);
ticketsCache.put(ticket.getTicketId(), ticket);
String ticketString = GRANTED_AUTHORITY_TICKET_PREFIX + ticket.getTicketId();
currentTicket.set(ticketString);
return ticketString;
}
public String validateTicket(String ticketString) throws AuthenticationException
{
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());
Ticket ticket = ticketsCache.get(key);
Ticket ticket = getTicketByTicketString(ticketString);
if (ticket == null)
{
throw new AuthenticationException("Missing ticket for " + ticketString);
@ -86,40 +105,70 @@ public class InMemoryTicketComponentImpl implements TicketComponent
}
// TODO: Recheck the user details here
// TODO: Strengthen ticket as GUID is predicatble
if(oneOff)
if (oneOff)
{
ticketsCache.remove(key);
ticketsCache.remove(getTicketKey(ticketString));
}
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.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 key = ticketString.substring(GRANTED_AUTHORITY_TICKET_PREFIX.length());
ticketsCache.remove(key);
}
public void invalidateTicketByUser(String userName)
{
Set<String> toRemove = new HashSet<String>();
for(String key: ticketsCache.getKeys())
for (String key : ticketsCache.getKeys())
{
Ticket ticket = ticketsCache.get(key);
if(ticket.getUserName().equals(userName))
if (ticket.getUserName().equals(userName))
{
toRemove.add(ticket.getTicketId());
}
}
for(String id: toRemove)
for (String id : toRemove)
{
ticketsCache.remove(id);
}
}
/**
* Ticket
* @author andyh
*
*/
public static class Ticket implements Serializable
{
private static final long serialVersionUID = -5904510560161261049L;
@ -132,18 +181,54 @@ public class InMemoryTicketComponentImpl implements TicketComponent
private String ticketId;
private String guid;
Ticket(boolean expires, Date expiryDate, String userName)
{
this.expires = expires;
this.expiryDate = expiryDate;
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
*
* @return
* @return - if expired
*/
boolean hasExpired()
{
@ -168,7 +253,9 @@ public class InMemoryTicketComponentImpl implements TicketComponent
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);
return (this.expires == t.expires)
&& this.expiryDate.equals(t.expiryDate) && this.userName.equals(t.userName)
&& this.ticketId.equals(t.ticketId);
}
public int hashCode()
@ -198,23 +285,59 @@ public class InMemoryTicketComponentImpl implements TicketComponent
}
/**
* 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 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 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()
{
String currentUser = getCurrentUserName();
@ -326,6 +327,18 @@ public class TestAuthenticationServiceImpl implements AuthenticationService
}
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()
{

View File

@ -34,14 +34,23 @@ package org.alfresco.repo.security.authentication;
public interface TicketComponent
{
/**
* Register a ticket
* Register a new ticket
*
* @param authentication
* @return
* @param userName
* @return - the ticket
* @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.
*
@ -62,13 +71,30 @@ public interface TicketComponent
* </ol>
* </ol>
*
* @param authentication
* @return
* @param ticket
* @return - the user name
* @throws AuthenticationException
*/
public String validateTicket(String ticket) throws AuthenticationException;
/**
* Invalidate the tickets by id
* @param ticket
*/
public void invalidateTicketById(String ticket);
/**
* Invalidate all user tickets
*
* @param 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
public String getCurrentTicket();
/**
* Get the current ticket as a string
* @return
*/
@Auditable
public String getNewTicket();
/**
* Remove the current security information
*