diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/oauth2/AbstractOAuth2AuthenticationProvider.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/oauth2/AbstractOAuth2AuthenticationProvider.java index 40a2eb09572..b8bdbfa324f 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/oauth2/AbstractOAuth2AuthenticationProvider.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/oauth2/AbstractOAuth2AuthenticationProvider.java @@ -10,6 +10,7 @@ import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo; import edu.harvard.iq.dataverse.authorization.AuthenticationProvider; import edu.harvard.iq.dataverse.authorization.AuthenticationProviderDisplayInfo; +import edu.harvard.iq.dataverse.util.BundleUtil; import javax.validation.constraints.NotNull; import java.io.IOException; @@ -127,28 +128,47 @@ public OAuth2UserRecord getUserRecord(String code, @NotNull OAuth20Service servi throws IOException, OAuth2Exception, InterruptedException, ExecutionException { OAuth2AccessToken accessToken = service.getAccessToken(code); - String userEndpoint = getUserEndpoint(accessToken); + + if ( ! accessToken.getScope().contains(scope) ) { + // We did not get the permissions on the scope we need. Abort and inform the user. + throw new OAuth2Exception(200, BundleUtil.getStringFromBundle("auth.providers.orcid.insufficientScope"), ""); + } - OAuthRequest request = new OAuthRequest(Verb.GET, userEndpoint); + OAuthRequest request = new OAuthRequest(Verb.GET, getUserEndpoint(accessToken)); request.setCharset("UTF-8"); service.signRequest(accessToken, request); - + Response response = service.execute(request); int responseCode = response.getCode(); String body = response.getBody(); - logger.log(Level.FINE, "In getUserRecord. Body: {0}", body); - - if ( responseCode == 200 ) { - final ParsedUserResponse parsed = parseUserResponse(body); - return new OAuth2UserRecord(getId(), parsed.userIdInProvider, - parsed.username, - OAuth2TokenData.from(accessToken), - parsed.displayInfo, - parsed.emails); + logger.log(Level.FINE, "In requestUserRecord. Body: {0}", body); + if ( responseCode == 200 && body != null ) { + return getUserRecord(body, accessToken, service); } else { throw new OAuth2Exception(responseCode, body, "Error getting the user info record."); } } + + /** + * Get the user record from the response body. + * Might be overriden by subclasses to add information from the access token response not included + * within the request response body. + * @param accessToken Access token used to create the request + * @param responseBody The response body = message from provider + * @param service Not used in base class, but may be used in overrides to lookup more data + * @return A complete record to be forwarded to user handling logic + * @throws OAuth2Exception When some lookup fails in overrides + */ + protected OAuth2UserRecord getUserRecord(@NotNull String responseBody, @NotNull OAuth2AccessToken accessToken, @NotNull OAuth20Service service) + throws OAuth2Exception { + + final ParsedUserResponse parsed = parseUserResponse(responseBody); + return new OAuth2UserRecord(getId(), parsed.userIdInProvider, + parsed.username, + OAuth2TokenData.from(accessToken), + parsed.displayInfo, + parsed.emails); + } @Override public boolean isUserInfoUpdateAllowed() { diff --git a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/oauth2/impl/OrcidOAuth2AP.java b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/oauth2/impl/OrcidOAuth2AP.java index b6ebd3dfe48..8d530e3b7e0 100644 --- a/src/main/java/edu/harvard/iq/dataverse/authorization/providers/oauth2/impl/OrcidOAuth2AP.java +++ b/src/main/java/edu/harvard/iq/dataverse/authorization/providers/oauth2/impl/OrcidOAuth2AP.java @@ -15,11 +15,8 @@ import edu.harvard.iq.dataverse.util.BundleUtil; import java.io.IOException; import java.io.StringReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; +import java.util.*; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -29,6 +26,7 @@ import javax.json.Json; import javax.json.JsonObject; import javax.json.JsonReader; +import javax.validation.constraints.NotNull; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -78,42 +76,31 @@ public DefaultApi20 getApiInstance() { } @Override - public OAuth2UserRecord getUserRecord(String code, String state, String redirectUrl) throws IOException, OAuth2Exception { - OAuth20Service service = getService(state, redirectUrl); - OAuth2AccessToken accessToken = service.getAccessToken(code); + final protected OAuth2UserRecord getUserRecord(@NotNull String responseBody, @NotNull OAuth2AccessToken accessToken, @NotNull OAuth20Service service) + throws OAuth2Exception { - if ( ! accessToken.getScope().contains(scope) ) { - // We did not get the permissions on the scope we need. Abort and inform the user. - throw new OAuth2Exception(200, BundleUtil.getStringFromBundle("auth.providers.orcid.insufficientScope"), ""); + // parse the main response + final ParsedUserResponse parsed = parseUserResponse(responseBody); + + // mixin org data, but optional + try { + Optional orgData = getOrganizationalData(accessToken, service); + if (orgData.isPresent()) { + parsed.displayInfo.setAffiliation(orgData.get().getAffiliation()); + parsed.displayInfo.setPosition(orgData.get().getPosition()); + } + } catch (IOException ex) { + logger.log(Level.WARNING, "Could not get affiliation data from ORCiD due to an IO problem: {0}", ex.getLocalizedMessage()); } + // mixin ORCiD not present in main response String orcidNumber = extractOrcidNumber(accessToken.getRawResponse()); - - final String userEndpoint = getUserEndpoint(accessToken); - - final OAuthRequest request = new OAuthRequest(Verb.GET, userEndpoint, service); - request.addHeader("Authorization", "Bearer " + accessToken.getAccessToken()); - request.setCharset("UTF-8"); - - final Response response = request.send(); - int responseCode = response.getCode(); - final String body = response.getBody(); - logger.log(Level.FINE, "In getUserRecord. Body: {0}", body); - - if ( responseCode == 200 ) { - final ParsedUserResponse parsed = parseUserResponse(body); - AuthenticatedUserDisplayInfo orgData = getOrganizationalData(userEndpoint, accessToken.getAccessToken(), service); - parsed.displayInfo.setAffiliation(orgData.getAffiliation()); - parsed.displayInfo.setPosition(orgData.getPosition()); - - return new OAuth2UserRecord(getId(), orcidNumber, - parsed.username, - OAuth2TokenData.from(accessToken), - parsed.displayInfo, - parsed.emails); - } else { - throw new OAuth2Exception(responseCode, body, "Error getting the user info record."); - } + + return new OAuth2UserRecord(getId(), orcidNumber, + parsed.username, + OAuth2TokenData.from(accessToken), + parsed.displayInfo, + parsed.emails); } @Override @@ -280,23 +267,29 @@ protected String extractOrcidNumber( String rawResponse ) throws OAuth2Exception } } - protected AuthenticatedUserDisplayInfo getOrganizationalData(String userEndpoint, String accessToken, OAuth20Service service) throws IOException { - final OAuthRequest request = new OAuthRequest(Verb.GET, userEndpoint.replace("/person", "/employments"), service); - request.addHeader("Authorization", "Bearer " + accessToken); - request.setCharset("UTF-8"); + protected Optional getOrganizationalData(OAuth2AccessToken accessToken, OAuth20Service service) throws IOException { - final Response response = request.send(); - int responseCode = response.getCode(); - final String responseBody = response.getBody(); + OAuthRequest request = new OAuthRequest(Verb.GET, getUserEndpoint(accessToken).replace("/person", "/employments")); + request.setCharset("UTF-8"); + service.signRequest(accessToken, request); - if ( responseCode != 200 ) { - // This is bad, but not bad enough to stop a signup/in process. - logger.log(Level.WARNING, "Cannot get affiliation data from ORCiD. Response code: {0} body:\n{1}\n/body", - new Object[]{responseCode, responseBody}); - return null; - - } else { - return parseActivitiesResponse(responseBody); + try { + Response response = service.execute(request); + int responseCode = response.getCode(); + String responseBody = response.getBody(); + + if (responseCode != 200 && responseBody != null) { + // This is bad, but not bad enough to stop a signup/in process. + logger.log(Level.WARNING, "Cannot get affiliation data from ORCiD. Response code: {0} body:\n{1}\n/body", + new Object[]{responseCode, responseBody}); + return Optional.empty(); + + } else { + return Optional.of(parseActivitiesResponse(responseBody)); + } + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.WARNING, "Could not get affiliation data from ORCiD due to threading problems."); + return Optional.empty(); } }