Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecated deactivated #1251

Merged
merged 8 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
import org.orcid.memberportal.service.assertion.config.ApplicationProperties;
import org.orcid.memberportal.service.assertion.domain.Assertion;
import org.orcid.memberportal.service.assertion.domain.adapter.AffiliationAdapter;
import org.orcid.memberportal.service.assertion.web.rest.errors.DeactivatedException;
import org.orcid.memberportal.service.assertion.web.rest.errors.DeprecatedException;
import org.orcid.memberportal.service.assertion.web.rest.errors.ORCIDAPIException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -74,13 +76,13 @@ public class OrcidAPIClient {

public OrcidAPIClient() throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(Affiliation.class, Distinction.class, Employment.class, Education.class, InvitedPosition.class,
Membership.class, Qualification.class, Service.class, OrcidError.class, NotificationPermission.class);
Membership.class, Qualification.class, Service.class, OrcidError.class, NotificationPermission.class);
this.jaxbMarshaller = jaxbContext.createMarshaller();
this.jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
this.httpClient = HttpClients.createDefault();
}

public String exchangeToken(String idToken) throws JSONException, ClientProtocolException, IOException {
public String exchangeToken(String idToken, String orcidId) throws JSONException, IOException, DeactivatedException {
HttpPost httpPost = new HttpPost(applicationProperties.getTokenExchange().getEndpoint());

List<NameValuePair> params = new ArrayList<NameValuePair>();
Expand All @@ -97,8 +99,14 @@ public String exchangeToken(String idToken) throws JSONException, ClientProtocol

if (statusCode != Status.OK.getStatusCode()) {
String responseString = EntityUtils.toString(response.getEntity());
LOG.error("Unable to exchange id_token: {}", responseString);
throw new ORCIDAPIException(response.getStatusLine().getStatusCode(), responseString);

if (responseString.contains("invalid_scope") && recordIsDeactivated(orcidId)) {
LOG.info("Deactivated profile detected: status code {}", statusCode);
throw new DeactivatedException();
} else {
LOG.error("Unable to exchange id_token: {}", responseString);
throw new ORCIDAPIException(response.getStatusLine().getStatusCode(), responseString);
}
}

String responseString = EntityUtils.toString(response.getEntity());
Expand All @@ -107,7 +115,7 @@ public String exchangeToken(String idToken) throws JSONException, ClientProtocol
return json.get("access_token").toString();
}

public String postAffiliation(String orcid, String accessToken, Assertion assertion) throws JAXBException {
public String postAffiliation(String orcid, String accessToken, Assertion assertion) throws DeprecatedException {
Affiliation orcidAffiliation = AffiliationAdapter.toOrcidAffiliation(assertion);
String affType = assertion.getAffiliationSection().getOrcidEndpoint();
LOG.info("Creating {} for {} with role title {}", affType, orcid, orcidAffiliation.getRoleTitle());
Expand All @@ -120,22 +128,22 @@ public String postAffiliation(String orcid, String accessToken, Assertion assert

try {
HttpResponse response = httpClient.execute(httpPost);
if (response.getStatusLine().getStatusCode() != Status.CREATED.getStatusCode()) {
if (response.getStatusLine().getStatusCode() == 409) {
throw new DeprecatedException();
} else if (response.getStatusLine().getStatusCode() != Status.CREATED.getStatusCode()) {
String responseString = EntityUtils.toString(response.getEntity());
LOG.error("Unable to create {} for {}. Status code: {}, error {}", affType, orcid, response.getStatusLine().getStatusCode(), responseString);
throw new ORCIDAPIException(response.getStatusLine().getStatusCode(), responseString);
}
String location = response.getFirstHeader("location").getValue();
return location.substring(location.lastIndexOf('/') + 1);
} catch (ClientProtocolException e) {
LOG.error("Unable to create affiliation in ORCID", e);
} catch (IOException e) {
LOG.error("Unable to create affiliation in ORCID", e);
}
return null;
}

public void putAffiliation(String orcid, String accessToken, Assertion assertion) throws JAXBException, IOException {
public void putAffiliation(String orcid, String accessToken, Assertion assertion) throws DeprecatedException, IOException {
Affiliation orcidAffiliation = AffiliationAdapter.toOrcidAffiliation(assertion);
String affType = assertion.getAffiliationSection().getOrcidEndpoint();
LOG.info("Updating affiliation with put code {} for {}", assertion.getPutCode(), orcid);
Expand All @@ -149,18 +157,20 @@ public void putAffiliation(String orcid, String accessToken, Assertion assertion
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpPut);
if (response.getStatusLine().getStatusCode() != Status.OK.getStatusCode()) {
if (response.getStatusLine().getStatusCode() == 409) {
throw new DeprecatedException();
} else if (response.getStatusLine().getStatusCode() != Status.OK.getStatusCode()) {
String responseString = EntityUtils.toString(response.getEntity());
LOG.error("Unable to update {} with putcode {} for {}. Status code: {}, error {}", affType, assertion.getPutCode(), orcid,
response.getStatusLine().getStatusCode(), responseString);
response.getStatusLine().getStatusCode(), responseString);
throw new ORCIDAPIException(response.getStatusLine().getStatusCode(), responseString);
}
} finally {
response.close();
}
}

public void deleteAffiliation(String orcid, String accessToken, Assertion assertion) throws IOException {
public void deleteAffiliation(String orcid, String accessToken, Assertion assertion) throws IOException, DeprecatedException {
String affType = assertion.getAffiliationSection().getOrcidEndpoint();
LOG.info("Deleting affiliation with putcode {} for {}", assertion.getPutCode(), orcid);

Expand All @@ -170,10 +180,12 @@ public void deleteAffiliation(String orcid, String accessToken, Assertion assert
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpDelete);
if (response.getStatusLine().getStatusCode() != Status.NO_CONTENT.getStatusCode()) {
if (response.getStatusLine().getStatusCode() == 409) {
throw new DeprecatedException();
} else if (response.getStatusLine().getStatusCode() != Status.NO_CONTENT.getStatusCode()) {
String responseString = EntityUtils.toString(response.getEntity());
LOG.error("Unable to delete {} with putcode {} for {}. Status code: {}, error {}", affType, assertion.getPutCode(), orcid,
response.getStatusLine().getStatusCode(), responseString);
response.getStatusLine().getStatusCode(), responseString);
throw new ORCIDAPIException(response.getStatusLine().getStatusCode(), responseString);
}
} finally {
Expand All @@ -182,18 +194,36 @@ public void deleteAffiliation(String orcid, String accessToken, Assertion assert
}

public String postNotification(NotificationPermission notificationPermission, String orcidId) throws JAXBException, IOException {
return internalPost(() -> {
return useInternalAccessToken(() -> {
return postNotificationPermission(notificationPermission, orcidId);
});
}

public String getOrcidIdForEmail(String email) throws IOException {
return internalPost(() -> {
return useInternalAccessToken(() -> {
return getOrcidIdFromRegistry(email);
});
}

private <T> T internalPost(Supplier<T> function) {
public boolean recordIsDeactivated(String orcidId) {
return useInternalAccessToken(() -> {
return checkRegistryForDeactivated(orcidId);
});
}

private boolean checkRegistryForDeactivated(String orcidId) {
HttpGet httpGet = new HttpGet(applicationProperties.getOrcidAPIEndpoint() + orcidId + "/person");
setJsonHeaders(httpGet, internalAccessToken);

try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
return response.getStatusLine().getStatusCode() == Status.CONFLICT.getStatusCode();
} catch (Exception e) {
LOG.error("Error checking registry for deactivated record {}", orcidId, e);
throw new RuntimeException(e);
}
}

private <T> T useInternalAccessToken(Supplier<T> function) {
initInternalAccessToken();
try {
return function.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@

public enum AssertionStatus {

USER_DENIED_ACCESS("User denied access"),
PENDING("Pending"),
IN_ORCID("In ORCID"),
USER_GRANTED_ACCESS("User granted access"),
USER_DELETED_FROM_ORCID("User deleted from ORCID"),
USER_REVOKED_ACCESS("User revoked access"),
ERROR_ADDING_TO_ORCID("Error adding to ORCID"),
ERROR_UPDATING_TO_ORCID("Error updating in ORCID"),
PENDING_RETRY("Pending retry in ORCID"),
USER_DENIED_ACCESS("User denied access"),
PENDING("Pending"),
IN_ORCID("In ORCID"),
USER_GRANTED_ACCESS("User granted access"),
USER_DELETED_FROM_ORCID("User deleted from ORCID"),
USER_REVOKED_ACCESS("User revoked access"),
ERROR_ADDING_TO_ORCID("Error adding to ORCID"),
ERROR_UPDATING_TO_ORCID("Error updating in ORCID"),
PENDING_RETRY("Pending retry in ORCID"),
ERROR_DELETING_IN_ORCID("Error deleting in ORCID"),
NOTIFICATION_REQUESTED("Notification requested"),
NOTIFICATION_SENT("Notification sent"),
NOTIFICATION_FAILED("Notification failed"),
PENDING_UPDATE("Pending update in ORCID");

PENDING_UPDATE("Pending update in ORCID"),
RECORD_DEACTIVATED_OR_DEPRECATED("Record is deactivated or deprecated");


private final String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@
import org.orcid.memberportal.service.assertion.upload.AssertionsUpload;
import org.orcid.memberportal.service.assertion.upload.AssertionsUploadSummary;
import org.orcid.memberportal.service.assertion.upload.impl.AssertionsCsvReader;
import org.orcid.memberportal.service.assertion.web.rest.errors.BadRequestAlertException;
import org.orcid.memberportal.service.assertion.web.rest.errors.ORCIDAPIException;
import org.orcid.memberportal.service.assertion.web.rest.errors.RegistryDeleteFailureException;
import org.orcid.memberportal.service.assertion.web.rest.errors.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -257,16 +255,16 @@ private boolean updateAssertionsSalesforceId(String from, String to, boolean rol
} catch (Exception e) {
LOG.error("Error bulk updating assertions from salesforce '" + from + "' to salesforce '" + to + "'", e);
if (rollback) {
LOG.info("Attempting to RESET assertion salesforce ids from '{}' to '{}'", new Object[] { to, from });
LOG.info("Attempting to RESET assertion salesforce ids from '{}' to '{}'", new Object[]{to, from});
boolean success = updateAssertionsSalesforceId(to, from, false);
if (success) {
LOG.info("Succeeded in RESETTING assertion salesforce ids from '{}' to '{}'", new Object[] { to, from });
LOG.info("Succeeded in RESETTING assertion salesforce ids from '{}' to '{}'", new Object[]{to, from});
return false;
} else {
LOG.error("Failed to reset assertions from '{}' to '{}'", new Object[] { to, from });
LOG.error("Failed to reset assertions from '{}' to '{}'", new Object[]{to, from});
LOG.error(
"Operation to update assertion salesforce ids from '{}' to '{}' has failed but there may be assertions with new sf id of '{}' in the database!",
new Object[] { from, to, to });
new Object[]{from, to, to});
return false;
}
}
Expand Down Expand Up @@ -413,9 +411,11 @@ public void postAssertionToOrcid(Assertion assertion) throws JAXBException {
} catch (ORCIDAPIException oae) {
LOG.info("Recieved orcid api exception");
storeError(assertion, oae.getStatusCode(), oae.getError(), AssertionStatus.ERROR_ADDING_TO_ORCID);
} catch (Exception e) {
LOG.error("Error posting assertion " + assertion.getId(), e);
storeError(assertion, 0, e.getMessage(), AssertionStatus.ERROR_ADDING_TO_ORCID);
} catch (DeactivatedException | DeprecatedException e) {
handleDeactivatedOrDeprecated(orcid, assertion);
} catch (Exception e1) {
LOG.error("Error posting assertion " + assertion.getId(), e1);
storeError(assertion, 0, e1.getMessage(), AssertionStatus.ERROR_ADDING_TO_ORCID);
}
} else if (deniedStatus != null) {
assertion.setStatus(deniedStatus.name());
Expand Down Expand Up @@ -459,6 +459,8 @@ public void putAssertionInOrcid(Assertion assertion) throws JAXBException {
assertion.setOrcidError(null);
assertion.setStatus(AssertionStatus.IN_ORCID.name());
assertionRepository.save(assertion);
} catch (DeactivatedException | DeprecatedException e) {
handleDeactivatedOrDeprecated(orcid, assertion);
} catch (ORCIDAPIException oae) {
storeError(assertion, oae.getStatusCode(), oae.getError(), AssertionStatus.ERROR_UPDATING_TO_ORCID);
LOG.info("Recieved orcid api exception");
Expand All @@ -474,16 +476,20 @@ public void putAssertionInOrcid(Assertion assertion) throws JAXBException {

private void deleteAssertionFromOrcidRegistry(Assertion assertion) throws RegistryDeleteFailureException {
Optional<OrcidRecord> record = orcidRecordService.findOneByEmail(assertion.getEmail());
String orcidId = record.get().getOrcid();

if (!checkRegistryDeletePreconditions(record, assertion)) {
throw new RegistryDeleteFailureException();
}

assertion.setLastSyncAttempt(Instant.now());

try {
LOG.info("Exchanging id token for {}", record.get().getOrcid());
String accessToken = orcidAPIClient.exchangeToken(record.get().getToken(assertion.getSalesforceId(), true));
orcidAPIClient.deleteAffiliation(record.get().getOrcid(), accessToken, assertion);
LOG.info("Exchanging id token for {}", orcidId);
String accessToken = orcidAPIClient.exchangeToken(record.get().getToken(assertion.getSalesforceId(), true), orcidId);
orcidAPIClient.deleteAffiliation(orcidId, accessToken, assertion);
} catch (DeactivatedException | DeprecatedException e) {
handleDeactivatedOrDeprecated(orcidId, assertion);
} catch (ORCIDAPIException oae) {
if (oae.getStatusCode() != 404) {
storeError(assertion, oae.getStatusCode(), oae.getError(), AssertionStatus.ERROR_DELETING_IN_ORCID);
Expand Down Expand Up @@ -524,7 +530,7 @@ private String getMemberAssertionStatsCsv(Map<String, MemberAssertionStats> stat
rows.add(row);
}

String[] headers = new String[] { "Member name", "Total affiliations", "Statuses" };
String[] headers = new String[]{"Member name", "Total affiliations", "Statuses"};
return new CsvWriter().writeCsv(headers, rows);
}

Expand Down Expand Up @@ -576,16 +582,26 @@ private AssertionStatus checkForTokenDeniedStatus(OrcidRecord orcidRecord, Asser
return null;
}

private String postToOrcidRegistry(String orcid, Assertion assertion, String idToken) throws JSONException, ClientProtocolException, IOException, JAXBException {
private String postToOrcidRegistry(String orcid, Assertion assertion, String idToken) throws JSONException, ClientProtocolException, IOException, JAXBException, DeprecatedException, DeactivatedException {
LOG.info("Exchanging id token for access token for assertion {}, orcid {}", assertion.getId(), orcid);
String accessToken = orcidAPIClient.exchangeToken(idToken);
String accessToken = orcidAPIClient.exchangeToken(idToken, orcid);
LOG.info("POST affiliation for {} and assertion id {}", orcid, assertion.getId());
return orcidAPIClient.postAffiliation(orcid, accessToken, assertion);
}

private void putInOrcidRegistry(String orcid, Assertion assertion, String idToken) throws JSONException, JAXBException, ClientProtocolException, IOException {
private void handleDeactivatedOrDeprecated(String orcid, Assertion assertion) {
List<Assertion> assertions = assertionRepository.findByEmail(assertion.getEmail());
assertions.forEach(a -> {
a.setStatus(AssertionStatus.RECORD_DEACTIVATED_OR_DEPRECATED.name());
assertionRepository.save(a);
});

orcidRecordService.deleteOrcidRecordByEmail(assertion.getEmail());
}

private void putInOrcidRegistry(String orcid, Assertion assertion, String idToken) throws JSONException, JAXBException, ClientProtocolException, IOException, DeprecatedException, DeactivatedException {
LOG.info("Exchanging id token for access token for assertion {}, orcid {}", assertion.getId(), orcid);
String accessToken = orcidAPIClient.exchangeToken(idToken);
String accessToken = orcidAPIClient.exchangeToken(idToken, orcid);
LOG.info("PUT affiliation with put-code {} for {} and assertion id {}", assertion.getPutCode(), orcid, assertion.getId());
orcidAPIClient.putAffiliation(orcid, accessToken, assertion);
}
Expand Down Expand Up @@ -614,7 +630,7 @@ private boolean checkRegistryDeletePreconditions(Optional<OrcidRecord> record, A
}

private void storeError(Assertion assertion, int statusCode, String error, AssertionStatus defaultErrorStatus) {
LOG.info("Error updating ORCID registry: assertion id - {}, orcid id - {], status code - {}, error - {}", assertion.getId(), assertion.getOrcidId(), statusCode, error);
LOG.info("Error updating ORCID registry: assertion id - {}, orcid id - {}, status code - {}, error - {}", assertion.getId(), assertion.getOrcidId(), statusCode, error);
JSONObject obj = new JSONObject();
obj.put("statusCode", statusCode);
obj.put("error", error);
Expand All @@ -633,18 +649,18 @@ private AssertionStatus getErrorStatus(Assertion assertion, AssertionStatus defa
int statusCode = json.getInt("statusCode");
String errorMessage = json.getString("error");
switch (statusCode) {
case 404:
return AssertionStatus.USER_DELETED_FROM_ORCID;
case 401:
return AssertionStatus.USER_REVOKED_ACCESS;
case 400:
if (errorMessage.contains("invalid_scope")) {
case 404:
return AssertionStatus.USER_DELETED_FROM_ORCID;
case 401:
return AssertionStatus.USER_REVOKED_ACCESS;
} else {
case 400:
if (errorMessage.contains("invalid_scope")) {
return AssertionStatus.USER_REVOKED_ACCESS;
} else {
return defaultError;
}
default:
return defaultError;
}
default:
return defaultError;
}
}

Expand Down
Loading
Loading