[CST-5669] Migrated OrcidRestController

This commit is contained in:
Luca Giamminonni
2022-04-08 18:27:00 +02:00
parent 3b153db00c
commit afa86658c9
2 changed files with 353 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest;
import java.net.URI;
import java.sql.SQLException;
import java.util.Optional;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.dspace.app.orcid.client.OrcidClient;
import org.dspace.app.orcid.model.OrcidTokenResponseDTO;
import org.dspace.app.orcid.service.OrcidSynchronizationService;
import org.dspace.app.profile.ResearcherProfile;
import org.dspace.app.rest.model.RestModel;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.content.service.ItemService;
import org.dspace.core.Context;
import org.dspace.services.ConfigurationService;
import org.dspace.util.UUIDUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Rest controller that store ORCID infos and handles redirect.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*/
@RequestMapping(value = "/api/" + RestModel.EPERSON + "/orcid")
@RestController
public class OrcidRestController {
@Autowired
private ConfigurationService configurationService;
@Autowired
private OrcidClient orcidClient;
@Autowired
private ItemService itemService;
@Autowired
private OrcidSynchronizationService orcidSynchronizationService;
@GetMapping(value = "/{itemId}")
public void linkProfileFromCode(HttpServletRequest request, HttpServletResponse response,
@RequestParam(name = "code") String code,
@PathVariable(name = "itemId") String itemId,
@RequestParam(name = "url") String url) throws Exception {
Context context = ContextUtil.obtainContext(request);
ResearcherProfile profile = findResearcherProfile(context, UUIDUtils.fromString(itemId))
.orElseThrow(() -> new ResourceNotFoundException("No profile found by item id: " + itemId));
OrcidTokenResponseDTO token = orcidClient.getAccessToken(code);
orcidSynchronizationService.linkProfile(context, profile.getItem(), token);
context.complete();
URI dspaceUiUrl = new URI(configurationService.getProperty("dspace.ui.url"));
response.sendRedirect(dspaceUiUrl.resolve(url).toString());
}
private Optional<ResearcherProfile> findResearcherProfile(Context context, UUID itemId) throws SQLException {
return Optional.ofNullable(itemService.find(context, itemId))
.map(ResearcherProfile::new);
}
public OrcidClient getOrcidClient() {
return orcidClient;
}
public void setOrcidClient(OrcidClient orcidClient) {
this.orcidClient = orcidClient;
}
}

View File

@@ -0,0 +1,263 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest;
import static org.dspace.app.matcher.MetadataValueMatcher.with;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.dspace.app.orcid.client.OrcidClient;
import org.dspace.app.orcid.model.OrcidTokenResponseDTO;
import org.dspace.app.rest.model.RestModel;
import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.builder.EPersonBuilder;
import org.dspace.builder.ItemBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.eperson.EPerson;
import org.dspace.services.ConfigurationService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Integration tests for {@link OrcidRestController}.
*
* @author Luca Giamminonni (luca.giamminonni at 4science.it)
*
*/
public class OrcidRestControllerIT extends AbstractControllerIntegrationTest {
private final static String ORCID = "0000-1111-2222-3333";
private final static String CODE = "123456";
private final static String ACCESS_TOKEN = "c41e37e5-c2de-4177-91d6-ed9e9d1f31bf";
private final static String REFRESH_TOKEN = "0062a9eb-d4ec-4d94-9491-95dd75376d3e";
private final static String[] ORCID_SCOPES = { "FirstScope", "SecondScope" };
@Autowired
private OrcidClient orcidClient;
private OrcidClient orcidClientMock = mock(OrcidClient.class);
@Autowired
private OrcidRestController orcidRestController;
@Autowired
private ConfigurationService configurationService;
private Collection profileCollection;
private EPerson user;
@Before
public void setup() {
orcidRestController.setOrcidClient(orcidClientMock);
context.turnOffAuthorisationSystem();
user = EPersonBuilder.createEPerson(context)
.withCanLogin(true)
.withPassword(password)
.withEmail("test@user.it")
.build();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent community")
.build();
profileCollection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Persons")
.withEntityType("Person")
.build();
context.restoreAuthSystemState();
}
@After
public void after() throws Exception {
orcidRestController.setOrcidClient(orcidClient);
}
@Test
public void testLinkProfileFromCodeProfileConfiguration() throws Exception {
context.turnOffAuthorisationSystem();
Item profileItem = ItemBuilder.createItem(context, profileCollection)
.withTitle("Test user")
.withDspaceObjectOwner("Test User", user.getID().toString())
.build();
context.restoreAuthSystemState();
when(orcidClientMock.getAccessToken(CODE)).thenReturn(buildOrcidTokenResponse(ORCID, ACCESS_TOKEN));
getClient().perform(get("/api/" + RestModel.EPERSON + "/orcid/{itemId}", profileItem.getID())
.param("code", CODE)
.param("url", "/home"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl(configurationService.getProperty("dspace.ui.url") + "/home"));
verify(orcidClientMock).getAccessToken(CODE);
verifyNoMoreInteractions(orcidClientMock);
profileItem = context.reloadEntity(profileItem);
assertThat(profileItem, notNullValue());
assertThat(profileItem.getMetadata(), hasItem(with("person.identifier.orcid", ORCID)));
assertThat(profileItem.getMetadata(), hasItem(with("dspace.orcid.access-token", ACCESS_TOKEN)));
assertThat(profileItem.getMetadata(), hasItem(with("dspace.orcid.refresh-token", REFRESH_TOKEN)));
assertThat(profileItem.getMetadata(), hasItem(with("dspace.orcid.scope", ORCID_SCOPES[0], 0)));
assertThat(profileItem.getMetadata(), hasItem(with("dspace.orcid.scope", ORCID_SCOPES[1], 1)));
user = context.reloadEntity(user);
assertThat(user.getNetid(), is(ORCID));
}
@Test
public void testLinkProfileFromCodeProfileConfigurationWithAnotherEPersonWithSameNetId() throws Exception {
context.turnOffAuthorisationSystem();
Item profileItem = ItemBuilder.createItem(context, profileCollection)
.withTitle("Test user")
.withDspaceObjectOwner("Test User", user.getID().toString())
.build();
EPersonBuilder.createEPerson(context)
.withCanLogin(true)
.withPassword(password)
.withEmail("test@anotherUser.it")
.withNetId(ORCID)
.build();
context.restoreAuthSystemState();
when(orcidClientMock.getAccessToken(CODE)).thenReturn(buildOrcidTokenResponse(ORCID, ACCESS_TOKEN));
getClient().perform(get("/api/" + RestModel.EPERSON + "/orcid/{itemId}", profileItem.getID())
.param("code", CODE)
.param("url", "/home"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl(configurationService.getProperty("dspace.ui.url") + "/home"));
verify(orcidClientMock).getAccessToken(CODE);
verifyNoMoreInteractions(orcidClientMock);
profileItem = context.reloadEntity(profileItem);
assertThat(profileItem, notNullValue());
assertThat(profileItem.getMetadata(), hasItem(with("person.identifier.orcid", ORCID)));
assertThat(profileItem.getMetadata(), hasItem(with("dspace.orcid.access-token", ACCESS_TOKEN)));
assertThat(profileItem.getMetadata(), hasItem(with("dspace.orcid.refresh-token", REFRESH_TOKEN)));
assertThat(profileItem.getMetadata(), hasItem(with("dspace.orcid.scope", ORCID_SCOPES[0], 0)));
assertThat(profileItem.getMetadata(), hasItem(with("dspace.orcid.scope", ORCID_SCOPES[1], 1)));
user = context.reloadEntity(user);
assertThat(user.getNetid(), nullValue());
}
@Test
public void testLinkProfileFromCodeWithoutCode() throws Exception {
context.turnOffAuthorisationSystem();
Item profileItem = ItemBuilder.createItem(context, profileCollection)
.withTitle("Test user")
.withDspaceObjectOwner("Test User", user.getID().toString())
.build();
context.restoreAuthSystemState();
when(orcidClientMock.getAccessToken(CODE)).thenReturn(buildOrcidTokenResponse(ORCID, ACCESS_TOKEN));
getClient().perform(get("/api/" + RestModel.EPERSON + "/orcid/{itemId}", profileItem.getID())
.param("url", "/home"))
.andExpect(status().isBadRequest());
verifyNoInteractions(orcidClientMock);
}
@Test
public void testLinkProfileFromCodeWithoutUrl() throws Exception {
context.turnOffAuthorisationSystem();
Item profileItem = ItemBuilder.createItem(context, profileCollection)
.withTitle("Test user")
.withDspaceObjectOwner("Test User", user.getID().toString())
.build();
context.restoreAuthSystemState();
when(orcidClientMock.getAccessToken(CODE)).thenReturn(buildOrcidTokenResponse(ORCID, ACCESS_TOKEN));
getClient().perform(get("/api/" + RestModel.EPERSON + "/orcid/{itemId}", profileItem.getID())
.param("code", CODE))
.andExpect(status().isBadRequest());
verifyNoInteractions(orcidClientMock);
}
@Test
public void testLinkProfileFromCodeWithProfileItemNotFound() throws Exception {
when(orcidClientMock.getAccessToken(CODE)).thenReturn(buildOrcidTokenResponse(ORCID, ACCESS_TOKEN));
getClient().perform(get("/api/" + RestModel.EPERSON + "/orcid/{itemId}", "af097328-ac1c-4a3e-9eb4-069897874910")
.param("code", CODE)
.param("url", "/home"))
.andExpect(status().isNotFound());
verifyNoInteractions(orcidClientMock);
}
@Test
public void testLinkProfileFromCodeWithInvalidProfile() throws Exception {
context.turnOffAuthorisationSystem();
Item profileItem = ItemBuilder.createItem(context, profileCollection)
.withTitle("Test user")
.build();
context.restoreAuthSystemState();
when(orcidClientMock.getAccessToken(CODE)).thenReturn(buildOrcidTokenResponse(ORCID, ACCESS_TOKEN));
getClient().perform(get("/api/" + RestModel.EPERSON + "/orcid/{itemId}", profileItem.getID())
.param("code", CODE)
.param("url", "/home"))
.andExpect(status().isBadRequest());
verifyNoInteractions(orcidClientMock);
}
private OrcidTokenResponseDTO buildOrcidTokenResponse(String orcid, String accessToken) {
OrcidTokenResponseDTO token = new OrcidTokenResponseDTO();
token.setAccessToken(accessToken);
token.setOrcid(orcid);
token.setTokenType("Bearer");
token.setRefreshToken(REFRESH_TOKEN);
token.setName("Test User");
token.setScope(String.join(" ", ORCID_SCOPES));
return token;
}
}