mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-16 17:55:15 +00:00
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:
parent
1544ecb1b6
commit
299b4d3434
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user