VIVO-769 Merge branch 'feature/orcid' into develop

This commit is contained in:
j2blake 2014-05-07 17:32:38 -04:00
parent 73fbe66534
commit 939950dcd4
25 changed files with 1551 additions and 1 deletions

View file

@ -198,6 +198,31 @@ VitroConnection.DataSource.validationQuery = SELECT 1
# languages.selectableLocales = en_US, es_GO # languages.selectableLocales = en_US, es_GO
# -----------------------------------------------------------------------------
# ORCID INTEGRATION
# -----------------------------------------------------------------------------
# orcid.clientId = 0000-0000-0000-000X
# orcid.clientPassword = 00000000-0000-0000-0000-000000000000
# orcid.webappBaseUrl = http://localhost:8080/vivo
# orcid.messageVersion = 1.0.23
# orcid.externalIdCommonName = VIVO Cornell Identifier
# ---- Setup for the sandbox ----
# orcid.publicApiBaseUrl = http://pub.sandbox-1.orcid.org/v1.1
# orcid.authorizedApiBaseUrl = http://api.sandbox-1.orcid.org/v1.1
# orcid.oauthAuthorizeUrl = http://sandbox-1.orcid.org/oauth/authorize
# orcid.oauthTokenUrl = http://api.sandbox-1.orcid.org/oauth/token
# ---- or for the mockorcid webapp ----
# orcid.publicApiBaseUrl = http://localhost:8080/mockorcid/mock/
# orcid.authorizedApiBaseUrl = http://localhost:8080/mockorcid/mock/
# orcid.oauthAuthorizeUrl = http://localhost:8080/mockorcid/mock/oauth/authorize
# orcid.oauthTokenUrl = http://localhost:8080/mockorcid/mock/oauth/token
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# OTHER OPTIONS # OTHER OPTIONS
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View file

@ -191,3 +191,7 @@ xsdlib
None None
---- ----
Trippi Trippi
ORCID API client
----------------
Written as part of the ORCID A&I grant. Source is available at https://github.com/j2blake/orcid-api-client

Binary file not shown.

View file

@ -85,5 +85,7 @@ org.apache.commons.fileupload.servlet.FileCleanerCleanup
# and the PermissionRegistry must already be set up. # and the PermissionRegistry must already be set up.
edu.cornell.mannlib.vitro.webapp.dao.jena.VClassGroupCache$Setup edu.cornell.mannlib.vitro.webapp.dao.jena.VClassGroupCache$Setup
edu.cornell.mannlib.vivo.orcid.OrcidContextSetup
# This should be near the end, because it will issue a warning if the connection to Solr times out. # This should be near the end, because it will issue a warning if the connection to Solr times out.
edu.cornell.mannlib.vitro.webapp.servlet.setup.SolrSmokeTest edu.cornell.mannlib.vitro.webapp.servlet.setup.SolrSmokeTest

View file

@ -1400,6 +1400,14 @@
<url-pattern>/searchService/*</url-pattern> <url-pattern>/searchService/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet>
<servlet-name>OrcidIntegrationController</servlet-name>
<servlet-class>edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>OrcidIntegrationController</servlet-name>
<url-pattern>/orcid/*</url-pattern>
</servlet-mapping>
<!-- ==================== tag libraries ============================== --> <!-- ==================== tag libraries ============================== -->
<jsp-config> <jsp-config>

View file

@ -0,0 +1,148 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#--
The body map contains the orcidInfo structure, which is set up like this:
orcidInfo
progress - a string set to one of these values: START, DENIED_AUTHENTICATE,
FAILED_AUTHENTICATE, GOT_PROFILE, ID_ALREADY_PRESENT, DENIED_ID,
FAILED_ID, ADDED_ID
individualUri - the URI of the person
profilePage - the URL of the individual's profile page
orcid - the confirmed ORCID (just xxxx-xxxx-xxxx-xxxx),
or the empty string.
orcidUri - the confirmed ORCID (full URI), or the empty string.
externalIds - empty if we haven't read their profile. Otherwise, a sequence
of maps, one for each external ID in their profile. These
might include SCOPUS ID, etc. Each map looks like this:
commonName - e.g., "VIVO Cornell"
reference - e.g., their VIVO localname
uri - e.g., their VIVO URI
hasVivoId - true, if we have read the profile and they already have
their VIVO URI as an external ID. False otherwise.
existingOrcids - A sequence of the ORCIDs (full URI) that we already associate
with this individual.
progressUrl - The URL to go to, that will continue this process. If the
process is complete or has failed, this is empty.
-->
<style TYPE="text/css">
#orcid-offer .step {
background-color: #F7F7F4;
border: 1px solid #cdcdcd;
border-radius: 10px;
padding: 0 1em 1em;
margin: 12px
}
#orcid-offer .links {
text-align: left;
margin-left: 12px;
}
#orcid-offer ul {
list-style: disc inside;
line-height: 28px;
}
#orcid-offer ul.inner {
list-style: none;
padding-left: 8px;
}
#orcid-offer li {
padding-left: 10px;
}
#orcid-offer .dimmed {
opacity:0.35;
filter:alpha(opacity=35);
}
span.completed {
color: #9a9a9a;
font-size: .8em;
}
</style>
<#assign orcidTextOne = "add an" />
<#assign orcidTextTwo = "Adding" />
<#if (orcidInfo.existingOrcids?size > 0) >
<#assign orcidTextOne = "confirm your" />
<#assign orcidTextTwo = "Confirming" />
</#if>
<#assign step2dimmed = (["START", "FAILED_AUTHENTICATE", "DENIED_AUTHENTICATE"]?seq_contains(orcidInfo.progress))?string("dimmed", "") />
<#assign continueAppears = (["START", "GOT_PROFILE"]?seq_contains(orcidInfo.progress))/>
<div>
<section id="orcid-offer" role="region">
<h2>Do you want to ${orcidTextOne} ORCID Identification?</h2>
<div class="step">
<#if "START" == orcidInfo.progress>
<h2>Step 1: ${orcidTextTwo} your ORCID ID</h2>
<ul>
<li>VIVO redirects you to ORCID's web site.</li>
<li>You log in to your ORCID account.
<ul class="inner"><li>If you don't have an account, you can create one.</li></ul>
</li>
<li>You tell ORCID that VIVO may read your ORCID Record. (one-time permission)</li>
<li>VIVO reads your ORCID Record.</li>
<li>VIVO notes that your ORCID ID is confirmed.</li>
</ul>
<#elseif "DENIED_AUTHENTICATE" == orcidInfo.progress>
<h2>Step 1: ${orcidTextTwo} your ORCID ID</h2>
<p>You denied VIVO's request to read your ORCID profile.</p>
<p>Confirmation can't continue.</p>
<#elseif "FAILED_AUTHENTICATE" == orcidInfo.progress>
<h2>Step 1: ${orcidTextTwo} your ORCID ID</h2>
<p>VIVO failed to read your ORCID profile.</p>
<p>Confirmation can't continue.</p>
<#else>
<h2>Step 1: ${orcidTextTwo} your ORCID ID <span class="completed">(step completed)</span></h2>
<p>Your ORCID ID is confirmed as ${orcidInfo.orcid}</p>
<p><a href="${orcidInfo.orcidUri}" target="_blank">View your ORCID profile page.</a></p>
</#if>
</div>
<div class="step ${step2dimmed}">
<#if "ID_ALREADY_PRESENT" == orcidInfo.progress>
<h2>Step 2 (recommended): Linking your ORCID Record to VIVO <span class="completed">(step completed)</span></h2>
<p>Your ORCID profile already includes a link to VIVO.</p>
<#elseif "DENIED_ID" == orcidInfo.progress>
<h2>Step 2 (recommended): Linking your ORCID Record to VIVO</h2>
<p>You denied VIVO's request to add an External ID to your ORCID profile.</p>
<p>Linking can't continue.</p>
<#elseif "FAILED_ID" == orcidInfo.progress>
<h2>Step 2 (recommended): Linking your ORCID Record to VIVO</h2>
<p>VIVO failed to add an External ID to your ORCID profile.</p>
<p>Linking can't continue.</p>
<#elseif "ADDED_ID" == orcidInfo.progress>
<h2>Step 2 (recommended): Linking your ORCID Record to VIVO <span class="completed">(step completed)</span></h2>
<p>Your ORCID profile is linked to VIVO</p>
<p><a href="${orcidInfo.orcidUri}" target="_blank">View your ORCID profile page.</a></p>
<#else>
<h2>Step 2 (recommended): Linking your ORCID Record to VIVO</h2>
<ul>
<li>VIVO redirects you to ORCID's web site</li>
<li>You tell ORCID that VIVO may add an "external ID" to your ORCID Record. (one-time permission)</li>
<li>VIVO adds the external ID.</li>
</ul>
</#if>
</div>
<div class=links>
<form method="GET" action="${orcidInfo.progressUrl}">
<p>
<#if continueAppears>
<input type="submit" name="submit" value="Continue <#if "START" == orcidInfo.progress>Step 1<#else>Step 2</#if>" class="submit"/>
or
</#if>
<a class="cancel" href="${orcidInfo.profilePage}">Return to your VIVO profile page</a>
</p>
</form>
</div>
</section>
</div>

View file

@ -0,0 +1,34 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#--
If authorized to confirm ORCID IDs, add the function that will replace the add link.
The OrcidIdDataGetter is attached to this template; it sets the orcidInfo structure,
which looks like this:
orcidInfo = map {
authorizedToConfirm: boolean
orcidUrl: link to the orcid controller
orcids: map of String to boolean [
orcid: String (full URI)
confirmed: boolean
]
}
-->
<#assign confirmThis = "" />
<#if orcidInfo??>
<#list orcidInfo.orcids?keys as key>
<#if "no" == orcidInfo.orcids[key]?string("yes","no") >
<#assign confirmThis = "Confirm the ID" />
</#if>
</#list>
<#if orcidInfo.authorizedToConfirm>
<script>
$(document).ready(function(){
$('#orcidId a.add-orcidId').replaceWith("<a class='add-orcidId' style='padding-left:20px' href='${orcidInfo.orcidUrl}'><#if orcidInfo.orcids?size == 0>Add an ID<#else>${confirmThis}</#if></a> ");
});
</script>
</#if>
</#if>

View file

@ -9,6 +9,13 @@
<#macro showStatement statement> <#macro showStatement statement>
<a href="${statement.value!}" title="ORCID iD" target="_blank">${statement.value!"ORCID iD not found"}</a> <a href="${statement.value!}" title="ORCID iD" target="_blank">${statement.value!"ORCID iD not found"}</a>
<#if orcidInfo??>
<#if (orcidInfo.orcids[statement.value])!false>
<span style="color:#FF7700">(confirmed)</span>
<#else>
<span style="color:#FF7700">(pending confirmation)</span>
</#if>
</#if>
</#macro> </#macro>

View file

@ -0,0 +1,2 @@
<http://orcid.org/1234-1231-1231-3333>
<http://vivoweb.org/ontology/core#validatedOrcidId> <http://vivo.mydomain.edu/individual/n4571> .

View file

@ -0,0 +1,14 @@
# $This file is distributed under the terms of the license in /doc/license.txt$
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix display: <http://vitro.mannlib.cornell.edu/ontologies/display/1.1#> .
#
# datagetter to fetch ORCID info for the person.
#
<freemarker:individual-orcidInterface.ftl> display:hasDataGetter display:orcidIdDataGetter .
display:orcidIdDataGetter
a <java:edu.cornell.mannlib.vivo.orcid.OrcidIdDataGetter> .

View file

@ -0,0 +1,10 @@
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix core: <http://vivoweb.org/ontology/core#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
core:confirmedOrcidId
a owl:ObjectProperty ;
rdfs:label "Orcid ID confirmation"@en-US ;
rdfs:comment "Indicates that the Orcid ID has been confirmed by this Person" ;
rdfs:range foaf:Person .

View file

@ -0,0 +1,107 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid;
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.AUTHORIZED_API_BASE_URL;
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.CALLBACK_PATH;
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.CLIENT_ID;
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.CLIENT_SECRET;
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.MESSAGE_VERSION;
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.OAUTH_AUTHORIZE_URL;
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.OAUTH_TOKEN_URL;
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.PUBLIC_API_BASE_URL;
import static edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting.WEBAPP_BASE_URL;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.DEFAULT_EXTERNAL_ID_COMMON_NAME;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.PROPERTY_EXTERNAL_ID_COMMON_NAME;
import java.util.EnumMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.orcidclient.OrcidClientException;
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext;
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting;
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
/**
* Setup for the ORCID interface.
*
* Note that the property for CLIENT_SECRET is "orcid.clientPassword". Since it
* ends in "password", it will not be displayed on the ShowConfiguration page.
*
* The CALLBACK_PATH is hardcoded. It is relative to the WEBAPP_BASE_URL, so it
* won't change.
*/
public class OrcidContextSetup implements ServletContextListener {
private static final Log log = LogFactory.getLog(OrcidContextSetup.class);
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext ctx = sce.getServletContext();
ConfigurationProperties props = ConfigurationProperties.getBean(ctx);
StartupStatus ss = StartupStatus.getBean(ctx);
if (props.getProperty("orcid.clientId", "").isEmpty()) {
ss.info(this, "ORCID Integration is not configured.");
return;
}
initializeOrcidClientContext(props, ss);
checkForCommonNameProperty(props, ss);
}
private void initializeOrcidClientContext(ConfigurationProperties props,
StartupStatus ss) {
try {
Map<Setting, String> settings = new EnumMap<>(Setting.class);
settings.put(CLIENT_ID, props.getProperty("orcid.clientId"));
settings.put(CLIENT_SECRET,
props.getProperty("orcid.clientPassword"));
settings.put(PUBLIC_API_BASE_URL,
props.getProperty("orcid.publicApiBaseUrl"));
settings.put(AUTHORIZED_API_BASE_URL,
props.getProperty("orcid.authorizedApiBaseUrl"));
settings.put(OAUTH_AUTHORIZE_URL,
props.getProperty("orcid.oauthAuthorizeUrl"));
settings.put(OAUTH_TOKEN_URL,
props.getProperty("orcid.oauthTokenUrl"));
settings.put(MESSAGE_VERSION,
props.getProperty("orcid.messageVersion"));
settings.put(WEBAPP_BASE_URL,
props.getProperty("orcid.webappBaseUrl"));
settings.put(CALLBACK_PATH, "orcid/callback");
OrcidClientContext.initialize(settings);
ss.info(this, "Context is: " + OrcidClientContext.getInstance());
} catch (OrcidClientException e) {
ss.warning(this, "Failed to initialize OrcidClientContent", e);
}
}
private void checkForCommonNameProperty(ConfigurationProperties props,
StartupStatus ss) {
if (StringUtils.isBlank(props
.getProperty(PROPERTY_EXTERNAL_ID_COMMON_NAME))) {
ss.warning(this, "'" + PROPERTY_EXTERNAL_ID_COMMON_NAME
+ "' is not set. " + "Using default value of '"
+ DEFAULT_EXTERNAL_ID_COMMON_NAME + "'");
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// Nothing to tear down.
}
}

View file

@ -0,0 +1,217 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.RequestIdentifiers;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.HasAssociatedIndividual;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.HasProfile;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.HasProxyEditingRights;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.IsRootUser;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.utils.SparqlQueryRunner;
import edu.cornell.mannlib.vitro.webapp.utils.SparqlQueryRunner.QueryParser;
import edu.cornell.mannlib.vitro.webapp.utils.dataGetter.DataGetter;
import edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController;
/**
* This data getter should be assigned to the template that renders the list
* view for ORCID IDs.
*
* Find out whether the user is authorized to confirm the ORCID IDs on this
* page. Find the list of ORCID IDs, and whether each has already been
* confirmed.
*
* The information is stored in the values map like this:
*
* <pre>
* orcidInfo = map {
* authorizedToConfirm: boolean
* orcids: map of String to boolean [
* orcid: String
* confirm: boolean
* ]
* }
* </pre>
*/
public class OrcidIdDataGetter implements DataGetter {
private static final Log log = LogFactory.getLog(OrcidIdDataGetter.class);
private static final Map<String, Object> EMPTY_RESULT = Collections
.emptyMap();
public static final String ORCID_ID = "http://vivoweb.org/ontology/core#orcidId";
public static final String ORCID_IS_CONFIRMED = "http://vivoweb.org/ontology/core#confirmedOrcidId";
private static final String QUERY_TEMPLATE = "SELECT ?orcid ?confirmed \n"
+ "WHERE { \n" //
+ " <%s> <%s> ?orcid . \n" //
+ " OPTIONAL { \n" //
+ " ?orcid <%s> ?confirmed . \n" //
+ " } \n" //
+ "}\n";
private final VitroRequest vreq;
public OrcidIdDataGetter(VitroRequest vreq) {
this.vreq = vreq;
}
@Override
public Map<String, Object> getData(Map<String, Object> valueMap) {
try {
String individualUri = findIndividualUri(valueMap);
if (individualUri == null) {
return EMPTY_RESULT;
}
boolean isAuthorizedToConfirm = figureIsAuthorizedtoConfirm(individualUri);
List<OrcidInfo> orcids = runSparqlQuery(individualUri);
return buildMap(isAuthorizedToConfirm, orcids, individualUri);
} catch (Exception e) {
log.warn("Failed to get orcID information", e);
return EMPTY_RESULT;
}
}
private String findIndividualUri(Map<String, Object> valueMap) {
try {
String uri = (String) valueMap.get("individualURI");
if (uri == null) {
log.warn("valueMap has no individualURI. Keys are: "
+ valueMap.keySet());
return null;
} else {
return uri;
}
} catch (Exception e) {
log.debug("has a problem finding the individualURI", e);
return null;
}
}
/**
* You are authorized to confirm an orcId only if you are a self-editor or
* root.
*/
private boolean figureIsAuthorizedtoConfirm(String individualUri) {
IdentifierBundle ids = RequestIdentifiers.getIdBundleForRequest(vreq);
boolean isSelfEditor = HasProfile.getProfileUris(ids).contains(
individualUri);
boolean isProxyEditor = HasProxyEditingRights.getProxiedPageUris(ids)
.contains(individualUri);
boolean isRoot = IsRootUser.isRootUser(ids);
return isRoot || isProxyEditor || isSelfEditor;
}
private List<OrcidInfo> runSparqlQuery(String individualUri) {
String queryStr = String.format(QUERY_TEMPLATE, individualUri,
ORCID_ID, ORCID_IS_CONFIRMED);
SparqlQueryRunner runner = new SparqlQueryRunner(vreq.getJenaOntModel());
return runner.executeSelect(new OrcidResultParser(), queryStr);
}
private Map<String, Object> buildMap(boolean isAuthorizedToConfirm,
List<OrcidInfo> orcids, String individualUri) {
Map<String, Boolean> confirmationMap = new HashMap<>();
for (OrcidInfo oInfo : orcids) {
confirmationMap.put(oInfo.getOrcid(), oInfo.isConfirmed());
}
Map<String, Object> orcidInfoMap = new HashMap<>();
orcidInfoMap.put("authorizedToConfirm", isAuthorizedToConfirm);
orcidInfoMap.put("orcidUrl", UrlBuilder.getUrl(
OrcidIntegrationController.PATH_DEFAULT, "individualUri",
individualUri));
orcidInfoMap.put("orcids", confirmationMap);
Map<String, Object> map = new HashMap<>();
map.put("orcidInfo", orcidInfoMap);
log.debug("Returning these values:" + map);
return map;
}
// ----------------------------------------------------------------------
// Helper classes
// ----------------------------------------------------------------------
/**
* Parse the results of the SPARQL query.
*/
private static class OrcidResultParser extends QueryParser<List<OrcidInfo>> {
@Override
protected List<OrcidInfo> defaultValue() {
return Collections.emptyList();
}
@Override
protected List<OrcidInfo> parseResults(String queryStr,
ResultSet results) {
List<OrcidInfo> orcids = new ArrayList<>();
while (results.hasNext()) {
try {
QuerySolution solution = results.next();
Resource orcid = solution.getResource("orcid");
RDFNode cNode = solution.get("confirmed");
log.debug("Result is orcid=" + orcid + ", confirmed="
+ cNode);
if (orcid != null && orcid.isURIResource()) {
boolean confirmed = (cNode != null);
orcids.add(new OrcidInfo(orcid.getURI(), confirmed));
}
} catch (Exception e) {
log.warn("Failed to parse the query result: " + queryStr, e);
}
}
return orcids;
}
}
/**
* A bean to hold info for each ORCID.
*/
static class OrcidInfo {
private final String orcid;
private final boolean confirmed;
public OrcidInfo(String orcid, boolean confirmed) {
this.orcid = orcid;
this.confirmed = confirmed;
}
public String getOrcid() {
return orcid;
}
public boolean isConfirmed() {
return confirmed;
}
@Override
public String toString() {
return "OrcidInfo[orcid=" + orcid + ", confirmed=" + confirmed
+ "]";
}
}
}

View file

@ -0,0 +1,124 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid.controller;
import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.OWL_THING;
import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.RDF_TYPE;
import static edu.cornell.mannlib.vivo.orcid.OrcidIdDataGetter.ORCID_ID;
import static edu.cornell.mannlib.vivo.orcid.OrcidIdDataGetter.ORCID_IS_CONFIRMED;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.TEMPLATE_CONFIRM;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.orcidclient.auth.AuthorizationManager;
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext;
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidMessage;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatement;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatementImpl;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao;
import edu.cornell.mannlib.vitro.webapp.dao.ObjectPropertyStatementDao;
import edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress;
/**
* Some utility methods for the handlers.
*/
public abstract class OrcidAbstractHandler {
private static final Log log = LogFactory
.getLog(OrcidAbstractHandler.class);
protected final VitroRequest vreq;
protected final OrcidClientContext occ;
protected final AuthorizationManager auth;
protected final OrcidConfirmationState state;
protected final UserAccount currentUser;
protected OrcidAbstractHandler(VitroRequest vreq) {
this.vreq = vreq;
this.occ = OrcidClientContext.getInstance();
this.auth = this.occ.getAuthorizationManager(vreq);
this.state = OrcidConfirmationState.fetch(vreq);
this.currentUser = LoginStatusBean.getCurrentUser(vreq);
}
protected Individual findIndividual() {
String uri = state.getIndividualUri();
try {
IndividualDao iDao = vreq.getWebappDaoFactory().getIndividualDao();
Individual individual = iDao.getIndividualByURI(uri);
if (individual == null) {
throw new IllegalStateException("Individual URI not valid: '"
+ uri + "'");
}
return individual;
} catch (Exception e) {
throw new IllegalStateException("Individual URI not valid: '" + uri
+ "'");
}
}
protected void recordConfirmation() {
String individualUri = state.getIndividualUri();
String orcidUri = state.getOrcidUri();
log.debug("Recording confirmation of ORCID '" + orcidUri + "' on '"
+ individualUri + "'");
ObjectPropertyStatement ops1 = new ObjectPropertyStatementImpl(
individualUri, ORCID_ID, orcidUri);
ObjectPropertyStatement ops2 = new ObjectPropertyStatementImpl(
orcidUri, RDF_TYPE, OWL_THING);
ObjectPropertyStatement ops3 = new ObjectPropertyStatementImpl(
orcidUri, ORCID_IS_CONFIRMED, individualUri);
ObjectPropertyStatementDao opsd = vreq.getWebappDaoFactory()
.getObjectPropertyStatementDao();
opsd.insertNewObjectPropertyStatement(ops1);
opsd.insertNewObjectPropertyStatement(ops2);
opsd.insertNewObjectPropertyStatement(ops3);
}
protected String cornellNetId() {
if (currentUser == null) {
return null;
}
String externalId = currentUser.getExternalAuthId();
if (externalId == null) {
return null;
}
if (externalId.trim().isEmpty()) {
return null;
}
return externalId;
}
protected ResponseValues show500InternalServerError(String message) {
log.error("Problem with ORCID request: " + message);
Map<String, Object> map = new HashMap<>();
map.put("title", "500 Internal Server Error");
map.put("errorMessage", message);
return new TemplateResponseValues("error-titled.ftl", map,
SC_INTERNAL_SERVER_ERROR);
}
protected ResponseValues showConfirmationPage(Progress p,
OrcidMessage... messages) {
state.progress(p, messages);
return showConfirmationPage();
}
protected ResponseValues showConfirmationPage() {
Map<String, Object> map = new HashMap<>();
map.put("orcidInfo", state.toMap());
return new TemplateResponseValues(TEMPLATE_CONFIRM, map);
}
}

View file

@ -0,0 +1,60 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid.controller;
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.ADD_EXTERNAL_ID;
import static edu.cornell.mannlib.orcidclient.orcidmessage.Visibility.PUBLIC;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.ADDED_ID;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.DENIED_ID;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.FAILED_ID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.orcidclient.OrcidClientException;
import edu.cornell.mannlib.orcidclient.actions.AddExternalIdAction;
import edu.cornell.mannlib.orcidclient.auth.AuthorizationStatus;
import edu.cornell.mannlib.orcidclient.beans.ExternalId;
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidMessage;
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
/**
* We should now be logged in to ORCID and authorized to add an external ID.
*/
public class OrcidAddExternalIdHandler extends OrcidAbstractHandler {
private static final Log log = LogFactory
.getLog(OrcidAddExternalIdHandler.class);
private AuthorizationStatus status;
private OrcidMessage profile;
protected OrcidAddExternalIdHandler(VitroRequest vreq) {
super(vreq);
}
public ResponseValues exec() throws OrcidClientException {
status = auth.getAuthorizationStatus(ADD_EXTERNAL_ID);
if (status.isSuccess()) {
addVivoId();
return showConfirmationPage(ADDED_ID, profile);
} else if (status.isDenied()) {
return showConfirmationPage(DENIED_ID);
} else {
return showConfirmationPage(FAILED_ID);
}
}
private void addVivoId() throws OrcidClientException {
Individual individual = findIndividual();
ExternalId externalId = new ExternalId().setCommonName("VIVO Cornell")
.setReference(individual.getLocalName())
.setUrl(individual.getURI()).setVisibility(PUBLIC);
log.debug("Adding external VIVO ID");
profile = new AddExternalIdAction().execute(externalId,
status.getAccessToken());
}
}

View file

@ -0,0 +1,66 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid.controller;
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.AUTHENTICATE;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.DENIED_AUTHENTICATE;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.FAILED_AUTHENTICATE;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.PATH_READ_PROFILE;
import java.net.URISyntaxException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.orcidclient.OrcidClientException;
import edu.cornell.mannlib.orcidclient.auth.AuthorizationStatus;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.RedirectResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
/**
* We offered the confirmation screen, and they decided to go ahead. Get
* authorization to authenticate them.
*
* We can't assume that they haven't been here before, so they might already
* have authorized, or denied authorization.
*/
public class OrcidAuthAuthenticateHandler extends OrcidAbstractHandler {
private static final Log log = LogFactory
.getLog(OrcidAuthAuthenticateHandler.class);
private AuthorizationStatus status;
public OrcidAuthAuthenticateHandler(VitroRequest vreq) {
super(vreq);
}
public ResponseValues exec() throws URISyntaxException,
OrcidClientException {
status = auth.getAuthorizationStatus(AUTHENTICATE);
if (status.isNone()) {
return seekAuthorizationForAuthenticate();
} else if (status.isSuccess()) {
return redirectToReadProfile();
} else if (status.isDenied()) {
return showConfirmationPage(DENIED_AUTHENTICATE);
} else {
return showConfirmationPage(FAILED_AUTHENTICATE);
}
}
private ResponseValues seekAuthorizationForAuthenticate()
throws OrcidClientException, URISyntaxException {
log.debug("Seeking authorization to authenticate.");
String returnUrl = occ.resolvePathWithWebapp(PATH_READ_PROFILE);
String seekUrl = auth.seekAuthorization(AUTHENTICATE, returnUrl);
return new RedirectResponseValues(seekUrl);
}
private ResponseValues redirectToReadProfile() throws URISyntaxException {
log.debug("Already authorized to authenticate.");
return new RedirectResponseValues(
occ.resolvePathWithWebapp(PATH_READ_PROFILE));
}
}

View file

@ -0,0 +1,66 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid.controller;
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.ADD_EXTERNAL_ID;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.DENIED_ID;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.FAILED_ID;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.PATH_ADD_EXTERNAL_ID;
import java.net.URISyntaxException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.orcidclient.OrcidClientException;
import edu.cornell.mannlib.orcidclient.auth.AuthorizationStatus;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.RedirectResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
/**
* We offered to add external IDs and they decided to go ahead. Get
* authorization.
*
* We can't assume that they haven't been here before, so they might already
* have authorized, or denied authorization.
*/
public class OrcidAuthExternalIdsHandler extends OrcidAbstractHandler {
private static final Log log = LogFactory
.getLog(OrcidAuthExternalIdsHandler.class);
private AuthorizationStatus status;
public OrcidAuthExternalIdsHandler(VitroRequest vreq) {
super(vreq);
}
public ResponseValues exec() throws URISyntaxException,
OrcidClientException {
status = auth.getAuthorizationStatus(ADD_EXTERNAL_ID);
if (status.isNone()) {
return seekAuthorizationForExternalId();
} else if (status.isSuccess()) {
return redirectToAddExternalId();
} else if (status.isDenied()) {
return showConfirmationPage(DENIED_ID);
} else {
return showConfirmationPage(FAILED_ID);
}
}
private ResponseValues seekAuthorizationForExternalId()
throws OrcidClientException, URISyntaxException {
log.debug("Seeking authorization to add external ID");
String returnUrl = occ.resolvePathWithWebapp(PATH_ADD_EXTERNAL_ID);
String seekUrl = auth.seekAuthorization(ADD_EXTERNAL_ID, returnUrl);
return new RedirectResponseValues(seekUrl);
}
private ResponseValues redirectToAddExternalId() throws URISyntaxException {
log.debug("Already authorized to add external ID.");
return new RedirectResponseValues(
occ.resolvePathWithWebapp(PATH_ADD_EXTERNAL_ID));
}
}

View file

@ -0,0 +1,55 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid.controller;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.orcidclient.OrcidClientException;
import edu.cornell.mannlib.orcidclient.auth.AuthorizationManager;
import edu.cornell.mannlib.orcidclient.auth.AuthorizationStatus;
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext;
/**
* Handle the callbacks during the OAuth dance.
*
* This is not like other handlers. It is created and invoked from doGet(), not
* from processRequest().
*/
public class OrcidCallbackHandler {
private static final Log log = LogFactory
.getLog(OrcidCallbackHandler.class);
private final HttpServletRequest req;
private final HttpServletResponse resp;
public OrcidCallbackHandler(HttpServletRequest req, HttpServletResponse resp) {
this.req = req;
this.resp = resp;
}
public void exec() throws IOException {
OrcidClientContext occ = OrcidClientContext.getInstance();
AuthorizationManager authManager = occ.getAuthorizationManager(req);
try {
AuthorizationStatus auth = authManager
.processAuthorizationResponse();
if (auth.isSuccess()) {
resp.sendRedirect(auth.getSuccessUrl());
} else {
resp.sendRedirect(auth.getFailureUrl());
}
} catch (OrcidClientException e) {
log.error("Invalid authorization response", e);
resp.sendError(SC_INTERNAL_SERVER_ERROR);
}
}
}

View file

@ -0,0 +1,250 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid.controller;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.START;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.PATH_AUTH_EXTERNAL_ID;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidIntegrationController.PATH_AUTH_AUTHENTICATE;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.orcidclient.orcidmessage.ExternalIdentifier;
import edu.cornell.mannlib.orcidclient.orcidmessage.ExternalIdentifiers;
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidBio;
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidId;
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidMessage;
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidProfile;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
/**
* Keep track of where we are in the Orcid confirmation process; what has been
* requested, and what has been returned.
*/
class OrcidConfirmationState {
private static final Log log = LogFactory
.getLog(OrcidConfirmationState.class);
// ----------------------------------------------------------------------
// The factory
// ----------------------------------------------------------------------
private static final String ATTRIBUTE_NAME = OrcidConfirmationState.class
.getName();
static OrcidConfirmationState fetch(HttpServletRequest req) {
HttpSession session = req.getSession();
Object o = session.getAttribute(ATTRIBUTE_NAME);
if (o instanceof OrcidConfirmationState) {
return (OrcidConfirmationState) o;
} else {
OrcidConfirmationState ocs = new OrcidConfirmationState();
session.setAttribute(ATTRIBUTE_NAME, ocs);
return ocs;
}
}
// ----------------------------------------------------------------------
// The instance
// ----------------------------------------------------------------------
public enum Progress {
START, DENIED_AUTHENTICATE, FAILED_AUTHENTICATE, GOT_PROFILE, ID_ALREADY_PRESENT, DENIED_ID, FAILED_ID, ADDED_ID
}
private static final Set<Progress> requiresMessage = EnumSet.of(
Progress.GOT_PROFILE, Progress.ADDED_ID);
private Progress progress;
private String individualUri;
private Set<String> existingOrcids;
private OrcidMessage profile;
private String profilePageUrl;
public void reset(String uri, String profileUrl) {
progress = START;
individualUri = uri;
existingOrcids = Collections.emptySet();
profile = null;
profilePageUrl = profileUrl;
}
public void setExistingOrcids(Set<String> existing) {
existingOrcids = new HashSet<>(existing);
}
public void progress(Progress p, OrcidMessage... messages) {
progress = p;
if (requiresMessage.contains(p)) {
if (messages.length != 1) {
throw new IllegalStateException("Progress to " + p
+ " requires an OrcidMessage");
}
profile = messages[0];
} else {
if (messages.length != 0) {
throw new IllegalStateException("Progress to " + p
+ " does not accept an OrcidMessage");
}
}
}
// ----------------------------------------------------------------------
// Convenience methods for extracting information from the profile.
// ----------------------------------------------------------------------
public String getProgress() {
return progress.toString();
}
public String getProgressUrl() {
switch (progress) {
case START:
return UrlBuilder.getUrl(PATH_AUTH_AUTHENTICATE);
case GOT_PROFILE:
return UrlBuilder.getUrl(PATH_AUTH_EXTERNAL_ID);
default:
return null;
}
}
public String getIndividualUri() {
return individualUri;
}
public String getProfilePageUrl() {
return profilePageUrl;
}
public String getOrcid() {
return getElementFromOrcidIdentifier("path");
}
public String getOrcidUri() {
return getElementFromOrcidIdentifier("uri");
}
public ExternalIdentifier getVivoId() {
for (ExternalIdentifier id : getExternalIds()) {
if (individualUri.equals(id.getExternalIdUrl().getValue())) {
return id;
}
}
return null;
}
public List<ExternalIdentifier> getExternalIds() {
OrcidProfile orcidProfile = getOrcidProfile();
if (orcidProfile == null) {
return Collections.emptyList();
}
OrcidBio bio = orcidProfile.getOrcidBio();
if (bio == null) {
return Collections.emptyList();
}
ExternalIdentifiers identifiers = bio.getExternalIdentifiers();
if (identifiers == null) {
return Collections.emptyList();
}
List<ExternalIdentifier> list = identifiers.getExternalIdentifier();
if (list == null) {
return Collections.emptyList();
}
return list;
}
private String getElementFromOrcidIdentifier(String elementName) {
OrcidProfile orcidProfile = getOrcidProfile();
if (orcidProfile == null) {
return "";
}
OrcidId id = orcidProfile.getOrcidIdentifier();
if (id == null) {
log.warn("There is no ORCID Identifier in the profile.");
return "";
}
List<JAXBElement<String>> idElements = id.getContent();
if (idElements != null) {
for (JAXBElement<String> idElement : idElements) {
QName name = idElement.getName();
if (name != null && elementName.equals(name.getLocalPart())) {
String value = idElement.getValue();
if (value != null) {
return value;
}
}
}
}
log.warn("Didn't find the element '' in the ORCID Identifier: " + idElements);
return "";
}
private OrcidProfile getOrcidProfile() {
if (profile == null) {
return null;
}
OrcidProfile orcidProfile = profile.getOrcidProfile();
if (orcidProfile == null) {
return null;
}
return orcidProfile;
}
public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
map.put("progress", progress.toString());
map.put("individualUri", individualUri);
map.put("profilePage", profilePageUrl);
map.put("orcid", getOrcid());
map.put("orcidUri", getOrcidUri());
map.put("hasVivoId", getVivoId() == null);
map.put("externalIds", formatExternalIds());
map.put("existingOrcids", existingOrcids);
String progressUrl = getProgressUrl();
if (progressUrl == null) {
map.put("progressUrl", "");
} else {
map.put("progressUrl", progressUrl);
}
return map;
}
private List<Map<String, String>> formatExternalIds() {
List<Map<String, String>> list = new ArrayList<>();
for (ExternalIdentifier id : getExternalIds()) {
Map<String, String> map = new HashMap<>();
map.put("commonName", id.getExternalIdCommonName().getContent());
map.put("reference", id.getExternalIdReference().getContent());
map.put("uri", id.getExternalIdUrl().getValue());
list.add(map);
}
return list;
}
}

View file

@ -0,0 +1,126 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid.controller;
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.ADD_EXTERNAL_ID;
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.READ_PROFILE;
import static edu.cornell.mannlib.vivo.orcid.OrcidIdDataGetter.ORCID_ID;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.RequestIdentifiers;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.HasProfile;
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatement;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.NotAuthorizedResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
/**
* A request came from the "Confirm" button on the individual profile. Get a
* fresh state object, clear the AuthorizationCache and show the confirmation
* page.
*/
public class OrcidDefaultHandler extends OrcidAbstractHandler {
private static final Log log = LogFactory.getLog(OrcidDefaultHandler.class);
private Individual individual;
private final Set<String> existingOrcids = new HashSet<>();
public OrcidDefaultHandler(VitroRequest vreq) {
super(vreq);
}
public ResponseValues exec() {
try {
initializeState();
initializeAuthorizationCache();
} catch (Exception e) {
log.error("No proper individual URI on the request", e);
return show400BadRequest(e);
}
if (!isAuthorized()) {
return showNotAuthorized();
}
return showConfirmationPage();
}
private void initializeState() {
String uri = vreq.getParameter("individualUri");
if (uri == null) {
throw new IllegalStateException(
"No 'individualUri' parameter on request.");
}
String profilePage = UrlBuilder.getIndividualProfileUrl(uri, vreq);
state.reset(uri, profilePage);
individual = findIndividual();
locateExistingOrcids();
state.setExistingOrcids(existingOrcids);
}
private void locateExistingOrcids() {
if (individual == null) {
return;
}
List<ObjectPropertyStatement> opss = individual
.getObjectPropertyStatements(ORCID_ID);
if (opss == null) {
return;
}
for (ObjectPropertyStatement ops : opss) {
existingOrcids.add(ops.getObjectURI());
}
}
private void initializeAuthorizationCache() {
auth.clearStatus(READ_PROFILE);
auth.clearStatus(ADD_EXTERNAL_ID);
}
private ResponseValues show400BadRequest(Exception e) {
Map<String, Object> map = new HashMap<>();
map.put("title", "400 Bad Request");
map.put("errorMessage", e.getMessage());
return new TemplateResponseValues("error-titled.ftl", map,
SC_BAD_REQUEST);
}
private boolean isAuthorized() {
// Only a self-editor is authorized.
IdentifierBundle ids = RequestIdentifiers.getIdBundleForRequest(vreq);
Collection<String> profileUris = HasProfile.getProfileUris(ids);
log.debug("Authorized? individualUri=" + state.getIndividualUri()
+ ", profileUris=" + profileUris);
return profileUris.contains(state.getIndividualUri());
}
private ResponseValues showNotAuthorized() {
UserAccount user = LoginStatusBean.getCurrentUser(vreq);
String userName = (user == null) ? "ANONYMOUS" : user.getEmailAddress();
return new NotAuthorizedResponseValues(userName
+ "is not authorized for ORCID operations on '" + individual
+ "'");
}
}

View file

@ -0,0 +1,20 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid.controller;
import edu.cornell.mannlib.orcidclient.OrcidClientException;
/**
* The OrcidConfirmationState is not as we expected. Probably deserves a 500
* error.
*/
public class OrcidIllegalStateException extends OrcidClientException {
public OrcidIllegalStateException(String message) {
super(message);
}
public OrcidIllegalStateException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,140 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid.controller;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext;
import edu.cornell.mannlib.orcidclient.context.OrcidClientContext.Setting;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.LogoutRedirector;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
/**
* New workflow:
*
* <pre>
* Default: clear status for both readProfile and addExternalIDs
* show intro screen orcidOffer.ftl
* The click "do it", goes to /getProfileAuth
* Or "return to profile"
* /getProfileAuth: If already authorized, redirect to /readProfile
* Else, do the dance, ending with /readProfile callback
* Denied? show orcidDenied.ftl
* Failed? show orcidFailed.ftl
* /readProfile: read the profile, store in status
* figure external ID options, show orcidOfferIds.ftl
* If they click "do it", goes /authExternalIds
* If they click "nah", return to profile
* /authExternalIds: if already authorized, redirect to /addExternalIds
* Else, do the dance, ending with /addExternalIds callback
* /addExternalIds add one or both IDs, store new profile in status
* show orcidSuccess.ftl with "return to profile" and "view profile" links.
* </pre>
*/
public class OrcidIntegrationController extends FreemarkerHttpServlet {
private static final Log log = LogFactory
.getLog(OrcidIntegrationController.class);
private final static String PATHINFO_CALLBACK = "/callback";
private final static String PATHINFO_AUTH_AUTHENTICATE = "/getAuthticateAuth";
private final static String PATHINFO_READ_PROFILE = "/readProfile";
private final static String PATHINFO_AUTH_EXTERNAL_ID = "/authExternalId";
private final static String PATHINFO_ADD_EXTERNAL_ID = "/addExternalId";
public final static String PATH_DEFAULT = "orcid";
final static String PATH_AUTH_AUTHENTICATE = path(PATHINFO_AUTH_AUTHENTICATE);
final static String PATH_READ_PROFILE = path(PATHINFO_READ_PROFILE);
final static String PATH_AUTH_EXTERNAL_ID = path(PATHINFO_AUTH_EXTERNAL_ID);
final static String PATH_ADD_EXTERNAL_ID = path(PATHINFO_ADD_EXTERNAL_ID);
static String path(String pathInfo) {
return PATH_DEFAULT + pathInfo;
}
final static String TEMPLATE_CONFIRM = "orcidConfirm.ftl";
public static final String PROPERTY_EXTERNAL_ID_COMMON_NAME = "orcid.externalIdCommonName";
public static final String DEFAULT_EXTERNAL_ID_COMMON_NAME = "VIVO Identifier";
/**
* Get in before FreemarkerHttpServlet for special handling.
*/
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
if (!isOrcidConfigured()) {
show404NotFound(resp);
}
if (PATHINFO_CALLBACK.equals(req.getPathInfo())) {
new OrcidCallbackHandler(req, resp).exec();
} else {
super.doGet(req, resp);
}
}
/**
* We return AUTHORIZED here, but we want the LogoutRedirector to know that
* the user should not remain on this page after logging out.
*/
@Override
protected AuthorizationRequest requiredActions(VitroRequest vreq) {
LogoutRedirector.recordRestrictedPageUri(vreq);
return AuthorizationRequest.AUTHORIZED;
}
/**
* Look at the path info and delegate to a handler.
*/
@Override
protected ResponseValues processRequest(VitroRequest vreq) throws Exception {
try {
String pathInfo = vreq.getPathInfo();
log.debug("Path info: " + pathInfo);
if (PATHINFO_AUTH_AUTHENTICATE.equals(pathInfo)) {
return new OrcidAuthAuthenticateHandler(vreq).exec();
} else if (PATHINFO_READ_PROFILE.equals(pathInfo)) {
return new OrcidReadProfileHandler(vreq).exec();
} else if (PATHINFO_AUTH_EXTERNAL_ID.equals(pathInfo)) {
return new OrcidAuthExternalIdsHandler(vreq).exec();
} else if (PATHINFO_ADD_EXTERNAL_ID.equals(pathInfo)) {
return new OrcidAddExternalIdHandler(vreq).exec();
} else {
return new OrcidDefaultHandler(vreq).exec();
}
} catch (Exception e) {
return new ExceptionResponseValues(e, SC_INTERNAL_SERVER_ERROR);
}
}
/**
* If the ORCID interface is configured, it should not throw an exception
* when asked for the value of a setting.
*/
private boolean isOrcidConfigured() {
try {
OrcidClientContext.getInstance().getSetting(Setting.CLIENT_ID);
return true;
} catch (Exception e) {
return false;
}
}
private void show404NotFound(HttpServletResponse resp) throws IOException {
resp.sendError(SC_NOT_FOUND);
}
}

View file

@ -0,0 +1,61 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vivo.orcid.controller;
import static edu.cornell.mannlib.orcidclient.actions.ApiAction.AUTHENTICATE;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.DENIED_AUTHENTICATE;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.FAILED_AUTHENTICATE;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.GOT_PROFILE;
import static edu.cornell.mannlib.vivo.orcid.controller.OrcidConfirmationState.Progress.ID_ALREADY_PRESENT;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.orcidclient.OrcidClientException;
import edu.cornell.mannlib.orcidclient.actions.ReadPublicBioAction;
import edu.cornell.mannlib.orcidclient.auth.AuthorizationStatus;
import edu.cornell.mannlib.orcidclient.orcidmessage.OrcidMessage;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
/**
* We should now know the user's ORCID, so read the user's public ORCID profile.
*/
public class OrcidReadProfileHandler extends OrcidAbstractHandler {
private static final Log log = LogFactory
.getLog(OrcidReadProfileHandler.class);
private AuthorizationStatus status;
private OrcidMessage profile;
protected OrcidReadProfileHandler(VitroRequest vreq) {
super(vreq);
}
public ResponseValues exec() throws OrcidClientException {
status = auth.getAuthorizationStatus(AUTHENTICATE);
if (status.isSuccess()) {
readProfile();
state.progress(GOT_PROFILE, profile);
recordConfirmation();
if (state.getVivoId() != null) {
state.progress(ID_ALREADY_PRESENT);
}
return showConfirmationPage();
} else if (status.isDenied()) {
return showConfirmationPage(DENIED_AUTHENTICATE);
} else {
return showConfirmationPage(FAILED_AUTHENTICATE);
}
}
private void readProfile() throws OrcidClientException {
profile = new ReadPublicBioAction().execute(status.getAccessToken()
.getOrcid());
log.debug("Read profile");
}
}

View file

@ -21,6 +21,10 @@
<#assign languageCount = 1> <#assign languageCount = 1>
</#if> </#if>
<#assign visRequestingTemplate = "foaf-person-wilma"> <#assign visRequestingTemplate = "foaf-person-wilma">
<#--add the VIVO-ORCID interface -->
<#include "individual-orcidInterface.ftl">
<section id="individual-intro" class="vcard person" role="region"> <section id="individual-intro" class="vcard person" role="region">
<section id="share-contact" role="region"> <section id="share-contact" role="region">

Binary file not shown.