package auth.impl;
import auth.Configuration;
import auth.IAuthModule;
import auth.WebSession;
import auth.models.User;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.i18n.Messages;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Result;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public abstract class AbstractAuthModule implements IAuthModule {
private static Logger logger = LoggerFactory.getLogger(AbstractAuthModule.class);
protected Map<String, ?> attrs;
protected User user;
protected List<Principal> pending = new ArrayList<Principal>();
protected List<Principal> principals = new ArrayList<Principal>();
protected boolean commitSucceeded = false;
protected Subject subject;
protected CallbackHandler callbackHandler;
protected Map<String, String> sharedState;
protected Map<String, ?> options;
/*
* (non-Javadoc)
*
* @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject,
* javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
*/
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = (Map<String, String>) sharedState;
this.options = options;
logger.trace("initialized: cb=" + callbackHandler + "; subject=" + subject
+ "; sharedState=" + sharedState + "; opts=" + options);
}
/*
* (non-Javadoc)
*
* @see controllers.auth.IAuthenticator#onAuthFailed(java.lang.String)
*/
@Override
public Result onAuthFailed(Exception e) {
logger.info("Failed authenticating user! Reason: " + (e != null ? e.getMessage() : "unknown"), e);
String respType = Configuration.getInstance().authFailedResponseType;
if (respType == Configuration.RESPONSE_TYPE_JSON) {
ObjectNode result = Json.newObject();
result.put("authResult", "fail");
result.put("errors", Messages.get("invalid_credentials"));
// or you can try: ok(string).as("application/json")
return Controller.ok(result);
} else if (respType == Configuration.RESPONSE_TYPE_REDIRECT) {
return Controller.redirect(Configuration.getInstance().urlAuthFailed);
}
return Controller.forbidden();
}
/*
* (non-Javadoc)
*
* @see controllers.auth.IAuthenticator#onAuthSucceeded(models.User)
*/
@Override
public Result onAuthSucceeded(Subject subject) {
User user = getUser(subject);
logger.info("User '" + user.name + "' successfully signed in!");
WebSession session = WebSession.newSession(Controller.ctx().session());
session.put("user", subject);
if (Configuration.getInstance().followOriginalUri) {
return Controller.redirect(Controller.ctx().request().uri());
} else {
return Controller.redirect(Configuration.getInstance().urlAuthSucceeded);
}
}
/**
*
* @param s
* @param p
*/
private void putPrincipal(Set<Principal> s, Principal p) {
logger.trace("added principal: " + p);
s.add(p);
principals.add(p);
}
/**
* <p>
* This method is called if the LoginContext's overall authentication succeeded (the relevant
* REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules succeeded).
* </p>
* <p>
* If this LoginModule's own authentication attempt succeeded (checked by retrieving the private
* state saved by the <code>login</code> method), then this method associates a number of
* <code>NTPrincipal</code>s with the <code>Subject</code> located in the
* <code>LoginModule</code>. If this LoginModule's own authentication attempted failed, then
* this method removes any state that was originally saved.
* </p>
*
* @return true if this LoginModule's own login and commit attempts succeeded, or false otherwise.
* @exception LoginException if the commit fails.
*/
@Override
public boolean commit() throws LoginException {
logger.debug("commit()");
if (pending == null) {
return false;
}
principals = new ArrayList<Principal>();
for (Principal p : pending) {
putPrincipal(subject.getPrincipals(), p);
}
commitSucceeded = true;
return true;
}
/**
* <p>
* This method is called if the LoginContext's overall authentication failed. (the relevant
* REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules did not succeed).
* <p>
* If this LoginModule's own authentication attempt succeeded (checked by retrieving the private
* state saved by the <code>login</code> and <code>commit</code> methods), then this method
* cleans up any state that was originally saved.
* <p>
*
* @exception LoginException if the abort fails.
* @return false if this LoginModule's own login and/or commit attempts failed, and true otherwise.
*/
@Override
public boolean abort() throws LoginException {
logger.debug("abort()");
if (pending.isEmpty()) {
return false;
} else if (!pending.isEmpty() && !commitSucceeded) {
pending = new ArrayList<Principal>();
} else {
logout();
}
return true;
}
/**
* Logout the user.
* <p>
* This method removes the <code>Principal</code>s that were added by the <code>commit</code>
* method.
* </p>
*
* @return true in all cases since this <code>LoginModule</code> should not be ignored.
* @exception LoginException if the logout fails.
*/
@Override
public boolean logout() throws LoginException {
logger.debug("logout()");
commitSucceeded = false;
// Remove all the principals we added
for (Principal p : principals) {
subject.getPrincipals().remove(p);
}
pending = null;
principals = null;
return true;
}
/**
* Get a String option from the module's options.
*
* @param name Name of the option
* @param dflt Default value for the option
* @return The String value of the options object.
*/
public String getOption(String name, String dflt) {
String opt = (String) options.get(name);
return opt == null ? dflt : opt;
}
/**
* Get a boolean option from the module's options.
*
* @param name Name of the option
* @param dflt Default value for the option
* @return The boolean value of the options object.
*/
protected boolean getOption(String name, boolean dflt) {
String opt = ((String) options.get(name));
if (opt == null) return dflt;
opt = opt.trim();
if (opt.equalsIgnoreCase("true") || opt.equalsIgnoreCase("yes") || opt.equals("1")) {
return true;
} else if (opt.equalsIgnoreCase("false") || opt.equalsIgnoreCase("no") || opt.equals("0")) {
return false;
} else {
return dflt;
}
}
/**
* Get a numeric option from the module's options.
*
* @param name Name of the option
* @param dflt Default value for the option
* @return The boolean value of the options object.
*/
public int getOption(String name, int dflt) {
String opt = ((String) options.get(name));
if (opt == null) return dflt;
try {
dflt = Integer.parseInt(opt);
} catch (Exception e) {
logger.info("Failed reading option.", e);
}
return dflt;
}
/**
* Helper method to get the User principal from the Subject.
* @param subject
* @return
*/
public static User getUser(Subject subject) {
if (subject != null) {
Set<User> userSet = subject.getPrincipals(User.class);
if (userSet != null && !userSet.isEmpty()) {
return userSet.iterator().next();
}
}
return null;
}
}