added pre and post hooks
This commit is contained in:
Scott Battaglia 2011-09-22 02:02:03 +00:00
parent 477fc582f0
commit 80693a3db9
2 changed files with 195 additions and 114 deletions

View File

@ -205,7 +205,7 @@ public class CasLoginModule implements LoginModule {
*/
public void initialize(final Subject subject, final CallbackHandler handler, final Map<String,?> state, final Map<String, ?> options) {
public final void initialize(final Subject subject, final CallbackHandler handler, final Map<String,?> state, final Map<String, ?> options) {
this.assertion = null;
this.callbackHandler = handler;
this.subject = subject;
@ -255,59 +255,89 @@ public class CasLoginModule implements LoginModule {
this.ticketValidator = createTicketValidator(ticketValidatorClass, options);
}
public boolean login() throws LoginException {
log.debug("Performing login.");
final NameCallback serviceCallback = new NameCallback("service");
final PasswordCallback ticketCallback = new PasswordCallback("ticket", false);
try {
this.callbackHandler.handle(new Callback[] { ticketCallback, serviceCallback });
} catch (final IOException e) {
log.info("Login failed due to IO exception in callback handler: " + e);
throw (LoginException) new LoginException("IO exception in callback handler: " + e).initCause(e);
} catch (final UnsupportedCallbackException e) {
log.info("Login failed due to unsupported callback: " + e);
throw (LoginException) new LoginException("Callback handler does not support PasswordCallback and TextInputCallback.").initCause(e);
}
if (ticketCallback.getPassword() != null) {
this.ticket = new TicketCredential(new String(ticketCallback.getPassword()));
final String service = CommonUtils.isNotBlank(serviceCallback.getName()) ? serviceCallback.getName() : this.service;
if (this.cacheAssertions) {
synchronized(ASSERTION_CACHE) {
if (ASSERTION_CACHE.get(ticket) != null) {
log.debug("Assertion found in cache.");
this.assertion = ASSERTION_CACHE.get(ticket);
}
}
}
if (this.assertion == null) {
log.debug("CAS assertion is null; ticket validation required.");
if (CommonUtils.isBlank(service)) {
log.info("Login failed because required CAS service parameter not provided.");
throw new LoginException("Neither login module nor callback handler provided required service parameter.");
}
try {
if (log.isDebugEnabled()) {
log.debug("Attempting ticket validation with service=" + service + " and ticket=" + ticket);
}
this.assertion = this.ticketValidator.validate(this.ticket.getName(), service);
} catch (final Exception e) {
log.info("Login failed due to CAS ticket validation failure: " + e);
throw (LoginException) new LoginException("CAS ticket validation failed: " + e).initCause(e);
}
}
log.info("Login succeeded.");
} else {
log.info("Login failed because callback handler did not provide CAS ticket.");
throw new LoginException("Callback handler did not provide CAS ticket.");
}
/**
* Operations to perform before doing login.
*
* @return true if you'd like login to continue, false otherwise.
*/
protected boolean preLogin() {
return true;
}
public boolean abort() throws LoginException {
/**
* This occurs after logout is processed.
*
* @param result the result from the login attempt.
*/
protected void postLogin(final boolean result) {
// template method
}
public final boolean login() throws LoginException {
log.debug("Performing login.");
if (!preLogin()) {
log.debug("preLogin failed.");
return false;
}
final NameCallback serviceCallback = new NameCallback("service");
final PasswordCallback ticketCallback = new PasswordCallback("ticket", false);
boolean result = false;
try {
try {
this.callbackHandler.handle(new Callback[] { ticketCallback, serviceCallback });
} catch (final IOException e) {
log.info("Login failed due to IO exception in callback handler: " + e);
throw (LoginException) new LoginException("IO exception in callback handler: " + e).initCause(e);
} catch (final UnsupportedCallbackException e) {
log.info("Login failed due to unsupported callback: " + e);
throw (LoginException) new LoginException("Callback handler does not support PasswordCallback and TextInputCallback.").initCause(e);
}
if (ticketCallback.getPassword() != null) {
this.ticket = new TicketCredential(new String(ticketCallback.getPassword()));
final String service = CommonUtils.isNotBlank(serviceCallback.getName()) ? serviceCallback.getName() : this.service;
if (this.cacheAssertions) {
synchronized(ASSERTION_CACHE) {
if (ASSERTION_CACHE.get(ticket) != null) {
log.debug("Assertion found in cache.");
this.assertion = ASSERTION_CACHE.get(ticket);
}
}
}
if (this.assertion == null) {
log.debug("CAS assertion is null; ticket validation required.");
if (CommonUtils.isBlank(service)) {
log.info("Login failed because required CAS service parameter not provided.");
throw new LoginException("Neither login module nor callback handler provided required service parameter.");
}
try {
if (log.isDebugEnabled()) {
log.debug("Attempting ticket validation with service=" + service + " and ticket=" + ticket);
}
this.assertion = this.ticketValidator.validate(this.ticket.getName(), service);
} catch (final Exception e) {
log.info("Login failed due to CAS ticket validation failure: " + e);
throw (LoginException) new LoginException("CAS ticket validation failed: " + e).initCause(e);
}
}
log.info("Login succeeded.");
} else {
log.info("Login failed because callback handler did not provide CAS ticket.");
throw new LoginException("Callback handler did not provide CAS ticket.");
}
result = true;
} finally {
postLogin(result);
}
return result;
}
public final boolean abort() throws LoginException {
if (this.ticket != null) {
this.ticket = null;
}
@ -317,74 +347,106 @@ public class CasLoginModule implements LoginModule {
return true;
}
public boolean commit() throws LoginException {
if (this.assertion != null) {
if (this.ticket != null) {
this.subject.getPrivateCredentials().add(this.ticket);
} else {
throw new LoginException("Ticket credential not found.");
}
final AssertionPrincipal casPrincipal = new AssertionPrincipal(this.assertion.getPrincipal().getName(), this.assertion);
this.subject.getPrincipals().add(casPrincipal);
// Add group containing principal as sole member
// Supports JBoss JAAS use case
final Group principalGroup = new SimpleGroup(this.principalGroupName);
principalGroup.addMember(casPrincipal);
this.subject.getPrincipals().add(principalGroup);
// Add group principal containing role data
final Group roleGroup = new SimpleGroup(this.roleGroupName);
for (final String defaultRole : defaultRoles) {
roleGroup.addMember(new SimplePrincipal(defaultRole));
}
final Map<String,Object> attributes = this.assertion.getPrincipal().getAttributes();
for (final String key : attributes.keySet()) {
if (this.roleAttributeNames.contains(key)) {
// Attribute value is Object if singular or Collection if plural
final Object value = attributes.get(key);
if (value instanceof Collection<?>) {
for (final Object o : (Collection<?>) value) {
roleGroup.addMember(new SimplePrincipal(o.toString()));
}
} else {
roleGroup.addMember(new SimplePrincipal(value.toString()));
}
}
}
this.subject.getPrincipals().add(roleGroup);
// Place principal name in shared state for downstream JAAS modules (module chaining use case)
this.sharedState.put(LOGIN_NAME, new Object()); // casPrincipal.getName());
if (log.isDebugEnabled()) {
if (log.isDebugEnabled()) {
log.debug("Created JAAS subject with principals: " + subject.getPrincipals());
}
}
if (this.cacheAssertions) {
if (log.isDebugEnabled()) {
log.debug("Caching assertion for principal " + this.assertion.getPrincipal());
}
ASSERTION_CACHE.put(this.ticket, this.assertion);
}
} else {
// Login must have failed if there is no assertion defined
// Need to clean up state
if (this.ticket != null) {
this.ticket = null;
}
}
/**
* Operations to perform before doing commit.
*
* @return true if you'd like commit to continue, false otherwise.
*/
protected boolean preCommit() {
return true;
}
public boolean logout() throws LoginException {
/**
* This occurs after commit is processed.
*
* @param result the result from the login attempt.
*/
protected void postCommit(final boolean result) {
// template method
}
public final boolean commit() throws LoginException {
if (!preCommit()) {
return false;
}
boolean result = false;
try {
if (this.assertion != null) {
if (this.ticket != null) {
this.subject.getPrivateCredentials().add(this.ticket);
} else {
throw new LoginException("Ticket credential not found.");
}
final AssertionPrincipal casPrincipal = new AssertionPrincipal(this.assertion.getPrincipal().getName(), this.assertion);
this.subject.getPrincipals().add(casPrincipal);
// Add group containing principal as sole member
// Supports JBoss JAAS use case
final Group principalGroup = new SimpleGroup(this.principalGroupName);
principalGroup.addMember(casPrincipal);
this.subject.getPrincipals().add(principalGroup);
// Add group principal containing role data
final Group roleGroup = new SimpleGroup(this.roleGroupName);
for (final String defaultRole : defaultRoles) {
roleGroup.addMember(new SimplePrincipal(defaultRole));
}
final Map<String,Object> attributes = this.assertion.getPrincipal().getAttributes();
for (final String key : attributes.keySet()) {
if (this.roleAttributeNames.contains(key)) {
// Attribute value is Object if singular or Collection if plural
final Object value = attributes.get(key);
if (value instanceof Collection<?>) {
for (final Object o : (Collection<?>) value) {
roleGroup.addMember(new SimplePrincipal(o.toString()));
}
} else {
roleGroup.addMember(new SimplePrincipal(value.toString()));
}
}
}
this.subject.getPrincipals().add(roleGroup);
// Place principal name in shared state for downstream JAAS modules (module chaining use case)
this.sharedState.put(LOGIN_NAME, new Object()); // casPrincipal.getName());
if (log.isDebugEnabled()) {
if (log.isDebugEnabled()) {
log.debug("Created JAAS subject with principals: " + subject.getPrincipals());
}
}
if (this.cacheAssertions) {
if (log.isDebugEnabled()) {
log.debug("Caching assertion for principal " + this.assertion.getPrincipal());
}
ASSERTION_CACHE.put(this.ticket, this.assertion);
}
} else {
// Login must have failed if there is no assertion defined
// Need to clean up state
if (this.ticket != null) {
this.ticket = null;
}
}
result = true;
} finally {
postCommit(result);
}
return result;
}
public final boolean logout() throws LoginException {
log.debug("Performing logout.");
if (!preLogout()) {
return false;
}
// Remove all CAS principals
removePrincipalsOfType(AssertionPrincipal.class);
removePrincipalsOfType(SimplePrincipal.class);
@ -394,9 +456,27 @@ public class CasLoginModule implements LoginModule {
removeCredentialsOfType(TicketCredential.class);
log.info("Logout succeeded.");
postLogout();
return true;
}
/**
* Happens before logout occurs.
*
* @return true if we should continue, false otherwise.
*/
protected boolean preLogout() {
return true;
}
/**
* Happens after logout.
*/
protected void postLogout() {
// template method
}
/**
* Creates a {@link TicketValidator} instance from a class name and map of property name/value pairs.

View File

@ -21,6 +21,7 @@ public final class SingleSignoutHandlerTests {
public void setUp() throws Exception {
handler = new SingleSignOutHandler();
handler.setLogoutParameterName(logoutParameterName);
handler.init();
request = new MockHttpServletRequest();
}