package auth.impl;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Http.Context;
import play.mvc.Result;
import auth.WebSession;
import auth.impl.callbackHandlers.HeadlessCallbackHandler;
import auth.impl.callbacks.AuthnResponseCallback;
import auth.models.User;
import auth.models.UserToken;
import auth.utils.SAMLUtils;
/**
* I am passing information from the login() to the onAuthSucceeded() via a fake principal PendingPrincipal.
* TODO: is there a better way?
*
*/
public class FederatedAuthModule extends AbstractAuthModule {
private static Logger logger = LoggerFactory.getLogger(FederatedAuthModule.class);
private static final String FEDERATED_AUTH_MODULE = "FederatedAuth";
private static final String TOKEN_PASSWORD = "cangetin";
// configuration
public String idpUrl; // url where we send AuthnRequest
private String derFile; // needed when messages are signed
private String pemFile; // needed when messages are signed
private String samlIssuerUrl; // must match the entityID from the SP's metadata description
private String assertionConsumerServiceUrl; // must match the <AssertionConsumerService>
private String idpSoapUrl; // url where we send AttributeQuery
private String samlUsername; // the attribute I look for in response
private SAMLUtils samlUtils;
/**
* @version $Revision: 1.1.2.3 $
* @author $Author: cristiand $
* @since $Date: 2013/03/13 20:34:04 $
*/
public class PendingPrincipal implements Principal {
private Map<String,String> principalAttrs = new HashMap<String, String>();
private String nameAttr;
/**
* @param idpUrl
*/
public PendingPrincipal(String nameAttr) {
this.nameAttr = nameAttr;
}
public void setAttribute(String nm, String value) {
principalAttrs.put(nm, value);
}
/* (non-Javadoc)
* @see java.security.Principal#getName()
*/
@Override
public String getName() {
return getAttribute(nameAttr);
}
public String getAttribute(String nm) {
return principalAttrs.get(nm);
}
}
/* (non-Javadoc)
* @see auth.IAuthModule#getCallbackHandler(play.mvc.Http.Context)
*/
@Override
public CallbackHandler getCallbackHandler(Context ctx) {
return new HeadlessCallbackHandler(ctx);
}
/* (non-Javadoc)
* @see auth.IAuthModule#getModuleName()
*/
@Override
public String getModuleName() {
return FEDERATED_AUTH_MODULE;
}
/* (non-Javadoc)
* @see javax.security.auth.spi.LoginModule#login()
*/
@Override
public boolean login() throws LoginException {
logger.debug("login()");
if (callbackHandler == null) {
throw new LoginException("Error: no CallbackHandler available!");
}
ArrayList<Callback> callbacks = new ArrayList<Callback>();
callbacks.add(new AuthnResponseCallback(samlUtils));
try {
// handle callbacks
Callback[] cb = new Callback[callbacks.size()];
callbackHandler.handle(callbacks.toArray(cb));
// process callbacks results
boolean respProcessed = ((AuthnResponseCallback)cb[0]).isResponseProcessed();
Http.Request req = ((AuthnResponseCallback)cb[0]).getOriginalRequest();
pending = new ArrayList<Principal>();
if (!respProcessed) {
// this is step 1 - create and send AuthnRequest
PendingPrincipal p = new PendingPrincipal("idpUrl");
p.setAttribute("idpUrl", idpUrl);
p.setAttribute("samlIssuerUrl", samlIssuerUrl);
p.setAttribute("assertionConsumerServiceUrl", assertionConsumerServiceUrl);
pending.add(p);
// even though it returns true, the user is not considered authenticated (see commit())
return true;
} else {
// step 2 - processed AuthResponse, extract username
String userid = ((AuthnResponseCallback)cb[0]).getAttribute(samlUsername);
String relayState = ((AuthnResponseCallback)cb[0]).getRelayState();
if (userid != null) {
User user = UserToken.createUserToken(userid, TOKEN_PASSWORD, req);
user.fullName = userid;
//user.email = email; user.phone = phone;
pending.add(user);
PendingPrincipal p = new PendingPrincipal("RelayState");
p.setAttribute("RelayState", relayState);
pending.add(p);
return true;
} else {
logger.info("Attribute " + samlUsername + " not retrieved from IdP");
}
}
} catch (Exception e) {
logger.info("failed user validation.", e);
}
return false;
}
/*
* (non-Javadoc)
*
* @see controllers.auth.IAuthenticator#onAuthSucceeded(models.User)
*/
@Override
public Result onAuthSucceeded(javax.security.auth.Subject subject) {
User user = getUser(subject);
if (user == null) {
// step 1 - send AuthnRequest
Set<PendingPrincipal> plst = subject.getPrincipals(PendingPrincipal.class);
PendingPrincipal p = plst.iterator().next();
String idpUrl = p.getName();
String issuerUrl = p.getAttribute("samlIssuerUrl");
String consumerUrl = p.getAttribute("assertionConsumerServiceUrl");
SAMLUtils samlUtils = SAMLUtils.getInstance();
String samlRequest = samlUtils.buildAuthnRequest(issuerUrl, consumerUrl, false);
String relayState = Context.current().request().uri();// ctx.request().uri();
return Controller.ok(views.html.samlRequestAuthn.render(idpUrl, samlRequest, relayState));
} else {
Set<PendingPrincipal> plst = subject.getPrincipals(PendingPrincipal.class);
PendingPrincipal p = plst.iterator().next();
String relayState = p.getName();
logger.info("User '" + user.name + "' successfully signed in! Redirecting to " + relayState);
WebSession session = WebSession.newSession(Context.current().session());
session.put("user", subject);
return Controller.redirect(relayState);
}
}
@Override
public void initialize(javax.security.auth.Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
super.initialize(subject, callbackHandler, sharedState, options);
idpUrl = getOption("idpUrl", null);
if (idpUrl == null) {
logger.error("idpUrl must be specified in JAAS configuration file");
}
assertionConsumerServiceUrl = getOption("assertionConsumerServiceUrl", null);
if (assertionConsumerServiceUrl == null) {
logger.error("assertionConsumerServiceUrl must be specified in JAAS configuration file");
}
samlIssuerUrl = getOption("samlIssuerUrl", null);
if (samlIssuerUrl == null) {
logger.error("samlIssuerUrl must be specified in JAAS configuration file");
}
samlUsername = getOption("userAttr", null);
if (samlUsername == null) {
logger.error("samlUsername must be specified in JAAS configuration file");
}
idpSoapUrl = getOption("idpSoapUrl", null);
pemFile = getOption("pemFile", null);
derFile = getOption("derFile", null);
boolean debug = getOption("debug", false);
logger.debug("idpUrl=" + idpUrl + "; consumerUrl=" + assertionConsumerServiceUrl + "; issuer="
+ samlIssuerUrl + "; userAttr=" + samlUsername + "; idPSoap=" + idpSoapUrl + "; pemFile="
+ pemFile + "; derFile=" + derFile);
samlUtils = SAMLUtils.getInstance();
samlUtils.setDERFileNm(derFile);
samlUtils.setPEMFileNm(pemFile);
samlUtils.setIdpSoapUrl(idpSoapUrl);
samlUtils.setPEMFileNm(samlIssuerUrl);
samlUtils.setSamlUsername(samlUsername);
samlUtils.setDebug(debug);
}
}