Skip to content

Commit

Permalink
Added Google Authentication. More work on ORCiD
Browse files Browse the repository at this point in the history
  • Loading branch information
michbarsinai committed Sep 11, 2016
1 parent e637f43 commit e146e21
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 51 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ scripts/api/py_api_wrapper/local-data/*
doc/sphinx-guides/build
faces-config.NavData
src/main/java/BuildNumber.properties
/nbproject/
/nbproject/
oauth-credentials.md

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.github.scribejava.core.oauth.OAuth20Service;
import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo;
import java.io.IOException;
import java.util.Objects;
import java.util.logging.Logger;

/**
Expand Down Expand Up @@ -36,6 +37,7 @@ public ParsedUserResponse(AuthenticatedUserDisplayInfo displayInfo, String userI
protected String userEndpoint;
protected String redirectUrl;
protected String imageUrl;
protected String scope;

public AbstractOAuth2Idp(){}

Expand All @@ -44,18 +46,21 @@ public AbstractOAuth2Idp(){}
protected abstract ParsedUserResponse parseUserResponse( String responseBody );

public OAuth20Service getService(String state) {
return (OAuth20Service) new ServiceBuilder()
ServiceBuilder svcBuilder = new ServiceBuilder()
.apiKey(getClientId())
.apiSecret(getClientSecret())
.state(state)
.callback(getRedirectUrl())
.scope(getUserEndpoint())
.build( getApiInstance() );
.callback(getRedirectUrl());
if ( scope != null ) {
svcBuilder.scope(scope);
}
return (OAuth20Service) svcBuilder.build( getApiInstance() );
}

public OAuth2UserRecord getUserRecord(String code, String state) throws IOException, OAuth2Exception {
OAuth20Service service = getService(state);
OAuth2AccessToken accessToken = service.getAccessToken(code);
Logger.getAnonymousLogger().info("Token: " + accessToken.getAccessToken()); // TODO remove.

final OAuthRequest request = new OAuthRequest(Verb.GET, getUserEndpoint(), service);
service.signRequest(accessToken, request);
Expand All @@ -66,7 +71,7 @@ public OAuth2UserRecord getUserRecord(String code, String state) throws IOExcept
ParsedUserResponse parsed = parseUserResponse(body);
return new OAuth2UserRecord(getId(), parsed.userIdInProvider, accessToken.getAccessToken(), parsed.displayInfo);
} else {
throw new OAuth2Exception(responseCode, body, "Error while exchanging OAuth code for an access token.");
throw new OAuth2Exception(responseCode, body, "Error getting the user info record.");
}
}

Expand Down Expand Up @@ -97,5 +102,38 @@ public String getImageUrl() {
public String getRedirectUrl() {
return redirectUrl;
}

@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + Objects.hashCode(this.id);
hash = 97 * hash + Objects.hashCode(this.clientId);
return hash;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if ( ! (obj instanceof AbstractOAuth2Idp)) {
return false;
}
final AbstractOAuth2Idp other = (AbstractOAuth2Idp) obj;
if (!Objects.equals(this.id, other.id)) {
return false;
}
if (!Objects.equals(this.clientId, other.clientId)) {
return false;
}
if (!Objects.equals(this.clientSecret, other.clientSecret)) {
return false;
}
return true;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import edu.harvard.iq.dataverse.authorization.AuthenticationRequest;
import edu.harvard.iq.dataverse.authorization.AuthenticationResponse;
import edu.harvard.iq.dataverse.authorization.providers.oauth2.identityproviders.GitHubOAuth2Idp;
import edu.harvard.iq.dataverse.authorization.providers.oauth2.identityproviders.GoogleOAuth2Idp;
import edu.harvard.iq.dataverse.authorization.providers.oauth2.identityproviders.OrcidPublicOAuth2Idp;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
Expand All @@ -20,7 +22,9 @@ public class OAuth2AuthenticationProvider implements AuthenticationProvider {

public OAuth2AuthenticationProvider() {
// TODO change this to be from the DB.
registedProvider( new GitHubOAuth2Idp() );
registerProvider( new OrcidPublicOAuth2Idp() );
registerProvider( new GitHubOAuth2Idp() );
registerProvider( new GoogleOAuth2Idp() );
}

@Override
Expand All @@ -39,7 +43,7 @@ public AuthenticationResponse authenticate(AuthenticationRequest request) {
throw new UnsupportedOperationException("Does not apply for OAuth.");
}

public final void registedProvider( AbstractOAuth2Idp p ) {
public final void registerProvider( AbstractOAuth2Idp p ) {
providers.put( p.getId(), p );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import edu.harvard.iq.dataverse.authorization.UserRecordIdentifier;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.util.StringUtil;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.EJB;
Expand All @@ -31,7 +31,7 @@ public class OAuth2Page implements Serializable {
static int counter = 0;

private static final Logger logger = Logger.getLogger(OAuth2Page.class.getName());
private static final long STATE_TIMEOUT = 1000*60*10;
private static final long STATE_TIMEOUT = 1000*60*15; // 15 minutes in msec
private int responseCode;
private String responseBody;
private OAuth2Exception error;
Expand All @@ -56,10 +56,18 @@ public String linkFor( String idpId ) {
public void exchangeCodeForToken() throws IOException {
HttpServletRequest req = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();

// TODO check for parameter "error", or catch OAuthException.
final String code = req.getParameter("code");
if ( code == null ) {
return;
logger.info("Code: '" + code + "'"); // TODO remove
if ( code == null || code.trim().isEmpty() ) {
try( BufferedReader rdr = req.getReader() ) {
StringBuilder sb = new StringBuilder();
String line;
while ( (line=rdr.readLine())!=null ) {
sb.append(line).append("\n");
}
error = new OAuth2Exception(-1, sb.toString(), "Remote system did not return an authorization code.");
return;
}
}

final String state = req.getParameter("state");
Expand Down Expand Up @@ -96,6 +104,7 @@ public void exchangeCodeForToken() throws IOException {
private AbstractOAuth2Idp getIdpFromState( String state ) {
String[] topFields = state.split("~",2);
if ( topFields.length != 2 ) {
logger.log(Level.INFO, "Wrong number of fields in state string", state);
return null;
}
AbstractOAuth2Idp idp = oauthPrv.getProvider( topFields[0] );
Expand All @@ -115,7 +124,7 @@ private AbstractOAuth2Idp getIdpFromState( String state ) {
return null;
}
} else {
logger.info("Invalid id field");
logger.log(Level.INFO, "Invalid id field: ''{0}''", stateFields[0]);
return null;
}
}
Expand All @@ -124,7 +133,8 @@ private String createState( AbstractOAuth2Idp idp ) {
if ( idp == null ) {
throw new IllegalArgumentException("idp cannot be null");
}
String base = idp.getId() + "~" + System.currentTimeMillis();
String base = idp.getId() + "~" + System.currentTimeMillis() + "~" + (int)java.lang.Math.round( java.lang.Math.random()*1000 );

String encrypted = StringUtil.encrypt(base, idp.clientSecret);
final String state = idp.getId() + "~" + encrypted;
return state;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ public GitHubOAuth2Idp() {
id = "github";
title = "GitHub";
clientId = "de1bf3127f3201d3e3a2"; // TODO load from config
clientSecret = "be71dc2176a37ae72b086dbc3223fc9da5a6d29c"; // TODO load from config
clientSecret = "WITHELD"; // TODO load from config
userEndpoint = "https://api.github.com/user";
redirectUrl = "http://localhost:8080/oauth2/callback.xhtml"; // TODO load from config
imageUrl = null;
// scope = "user";
}

@Override
Expand All @@ -39,7 +40,7 @@ protected ParsedUserResponse parseUserResponse( String responseBody ) {

AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(
response.getString("name",""),
"", // Github has no concept of family name
"", // Github has no concept of a family name
response.getString("email",""),
response.getString("company",""),
""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package edu.harvard.iq.dataverse.authorization.providers.oauth2.identityproviders;

import com.github.scribejava.apis.GoogleApi20;
import com.github.scribejava.core.builder.api.BaseApi;
import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo;
import edu.harvard.iq.dataverse.authorization.providers.oauth2.AbstractOAuth2Idp;
import java.io.StringReader;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;

/**
*
* @author michael
*/
public class GoogleOAuth2Idp extends AbstractOAuth2Idp {

public GoogleOAuth2Idp() {
id = "google";
title = "Google";
clientId = "372743369730-il7ohtgdemnvd2pqto8thluqgmpvu94b.apps.googleusercontent.com";
clientSecret = "WITHELD";
redirectUrl = "http://localhost:8080/oauth/callback.xhtml";
scope = "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email";
userEndpoint = "https://www.googleapis.com/oauth2/v2/userinfo";
}

@Override
public BaseApi getApiInstance() {
return GoogleApi20.instance();
}

@Override
protected ParsedUserResponse parseUserResponse(String responseBody) {
try ( StringReader rdr = new StringReader(responseBody);
JsonReader jrdr = Json.createReader(rdr) ) {
JsonObject response = jrdr.readObject();

AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(
response.getString("given_name",""),
response.getString("family_name",""),
response.getString("email",""),
"",
""
);
return new ParsedUserResponse(displayInfo, response.getString("id"));
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package edu.harvard.iq.dataverse.authorization.providers.oauth2.identityproviders;

import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.extractors.OAuth2AccessTokenExtractor;
import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
import com.github.scribejava.core.extractors.TokenExtractor;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.Response;
import java.io.IOException;
import java.util.logging.Logger;

/**
* Adaptor for ORCiD OAuth identity Provider.
* @author michael
*/
public class OrcidApi extends DefaultApi20 {

/**
* The instance holder pattern allows for lazy creation of the intance.
*/
private static class InstanceHolder {
private static final OrcidApi INSTANCE = new OrcidApi();
}

public static OrcidApi instance() {
return InstanceHolder.INSTANCE;
}

protected OrcidApi(){}

@Override
public String getAccessTokenEndpoint() {
return "https://orcid.org/oauth/token";
}

@Override
protected String getAuthorizationBaseUrl() {
return "https://orcid.org/oauth/authorize";
}

@Override
public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
return OAuth2AccessTokenJsonExtractor.instance();
}

static class TE extends OAuth2AccessTokenJsonExtractor {

@Override
public OAuth2AccessToken extract(String response) {
Logger.getAnonymousLogger().info("Response:");
Logger.getAnonymousLogger().info(response);
Logger.getAnonymousLogger().info("/Response");
return super.extract(response);
}

}

}
Loading

0 comments on commit e146e21

Please sign in to comment.