package be.authtest.vaadin.oauth;
import java.io.IOException;
import java.security.Principal;
import java.security.acl.Group;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Map;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.TextInputCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.sql.DataSource;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.auth.spi.AbstractServerLoginModule;
import org.scribe.builder.ServiceBuilder;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
import com.google.gson.Gson;
/**
* JDBC login module to authenticate Google users with JAAS
*
*/
public class GoogleLoginModule extends AbstractServerLoginModule {
private static final String OPTION_JNDI = "dsJndiName";
private static final String OPTION_ROLESQUERY = "rolesQuery";
private static final String OPTION_CALLBACKURL = "callbackUrl";
private static final String OPTION_GPLUSKEY = "gplusKey";
private static final String OPTION_GPLUSSECRET = "gplusSecret";
private static final String OPTION_URL = "gplusUrl";
private static final int CODE = 0;
private static final Logger logger = LogManager.getLogger(GoogleLoginModule.class);
OAuthService service;
// private principals
private String username = "";
private ArrayList roleNames = null;
// Options from standalone-full.xml
private String dsJndiName = "";
private String rolesQuery = "";
private String callbackUrl = "";
private String gplusUrl = "";
public GoogleLoginModule() {
logger.debug("Instantiating login module {} {}", getClass().getName(), this);
super.addValidOptions(new String[]{OPTION_JNDI,OPTION_ROLESQUERY,OPTION_CALLBACKURL,OPTION_GPLUSKEY,OPTION_GPLUSSECRET,OPTION_URL});
}
@Override
public void initialize(Subject _subject, CallbackHandler _callbackHandler, Map _sharedState, Map _options) {
super.initialize(_subject, _callbackHandler, _sharedState, _options);
this.dsJndiName = (String)this.options.get(OPTION_JNDI);
this.rolesQuery = (String)this.options.get(OPTION_ROLESQUERY);
this.callbackUrl = (String)this.options.get(OPTION_CALLBACKURL);
String gplusKey = (String)this.options.get(OPTION_GPLUSKEY);
String gplusSecret = (String)this.options.get(OPTION_GPLUSSECRET);
this.gplusUrl = (String)this.options.get(OPTION_URL);
this.service = createService(gplusKey, gplusSecret);
}
/**
* This method is called via the JAAS login
* LoginContext loginContext = new LoginContext("GoogleLogin", callbackHandler);
* loginContext.login();
* The TextCallback is populated with the Google return code
* (prompts are not used).
*
*/
@Override
public boolean login() throws LoginException {
Callback[] callbackArray = new Callback[1];
callbackArray[CODE] = new TextInputCallback("code");
try {
this.callbackHandler.handle(callbackArray);
String code = ((TextInputCallback) callbackArray[CODE]).getText();
Verifier v = new Verifier(code);
Token t = getService().getAccessToken(null, v);
//OAuthRequest r = new OAuthRequest(Verb.GET, "https://www.googleapis.com/plus/v1/people/me");
OAuthRequest r = new OAuthRequest(Verb.GET, getGplusUrl());
getService().signRequest(t, r);
Response resp = r.send();
GooglePlusAnswer answer = new Gson().fromJson(resp.getBody(), GooglePlusAnswer.class);
if (!verifyGoogleAnswer(answer)) {
throw new FailedLoginException("Authorization failure for user {" + answer.emails[0].value + "}");
}
// Set the principal
this.username = answer.emails[0].value;
this.roleNames = getUserRoles();
// Call the super method
super.loginOk = true;
return super.loginOk;
} catch (IOException ioex) {
ioex.printStackTrace();
} catch (SQLException sex) {
sex.printStackTrace();
}
catch (UnsupportedCallbackException unex) {
unex.printStackTrace();
}
return false;
}
@Override
protected Group[] getRoleSets() throws LoginException {
SimpleGroup roles = new SimpleGroup("Roles");
Group[] roleSets = {roles};
for (String roleName : getRoleNames()) {
roles.addMember(new SimplePrincipal(roleName));
}
return roleSets;
}
private ArrayList getUserRoles() throws LoginException, SQLException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
ArrayList _roles = new ArrayList();
try {
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(this.dsJndiName);
conn = ds.getConnection();
// Get the password
ps = conn.prepareStatement(this.rolesQuery);
int i = 1;
ps.setString(i++, this.username);
rs = ps.executeQuery();
while (rs.next()) {
_roles.add(rs.getString(1));
}
if (_roles.isEmpty()) {
throw new FailedLoginException("No roles found in Principals");
}
} catch (NamingException ex) {
throw new LoginException(ex.toString(true));
} catch (SQLException ex) {
throw new LoginException(ex.toString());
} finally {
if (rs != null) {
rs.close();
}
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
}
return _roles;
}
private OAuthService getService() {
return this.service;
}
private boolean verifyGoogleAnswer(GooglePlusAnswer answer) {
if (answer != null && answer.emails.length > 0 && StringUtils.isNotEmpty(answer.emails[0].value)) {
return true;
}
return false;
}
/**
* Create a service to retrieve a Token from Google
* @return
*/
private OAuthService createService(String gplusKey, String gplusSecret) {
ServiceBuilder sb = new ServiceBuilder();
sb.provider(Google2Api.class);
sb.apiKey(gplusKey);
sb.apiSecret(gplusSecret);
sb.scope("email");
sb.callback(getCallbackUrl());
return sb.build();
}
@Override
protected Principal getIdentity() {
Principal principal = new SimplePrincipal(getUsername());
return principal;
}
private String getCallbackUrl() {
return this.callbackUrl;
}
public String getGplusUrl() {
return this.gplusUrl;
}
/////////// Getters for principals used by getRoleSets() and commit()
public String getUsername() {
return this.username;
}
public ArrayList getRoleNames() {
return this.roleNames;
}
}