Skip to content
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
13 changes: 9 additions & 4 deletions server/src/main/java/invite/eduid/EduID.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package invite.eduid;

import invite.exception.RemoteException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -29,14 +30,18 @@ public EduID(@Value("${myconext.uri}") String uri,
this.headers = initHttpHeaders();
}

public Optional<String> provisionEduid(EduIDProvision eduIDProvision) {
public String provisionEduid(EduIDProvision eduIDProvision) {
HttpEntity<EduIDProvision> requestEntity = new HttpEntity<>(eduIDProvision, headers);
try {
ResponseEntity<EduIDProvision> responseEntity = restTemplate.exchange(uri, HttpMethod.POST, requestEntity, EduIDProvision.class);
return Optional.of(responseEntity.getBody().getEduIDValue());
String eduIDValue = responseEntity.getBody().getEduIDValue();

LOG.info(String.format("eduID %s for institution %s is eduID %s after eduID provisioning",
eduIDProvision.getEduIDValue(), eduIDProvision.getInstitutionGUID(), eduIDValue));

return eduIDValue;
} catch (RuntimeException e) {
LOG.error("Error in provisionEduid", e);
return Optional.empty();
throw new RemoteException(HttpStatus.BAD_REQUEST, "Error in provisionEduid", e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,10 @@ public Optional<GraphResponse> newUserRequest(User user) {
.filter(provisioning -> this.remoteProvisionedUserRepository.findByManageProvisioningIdAndUser(provisioning.getId(), user)
.isEmpty())
.forEach(provisioning -> {
UserRequest request = new UserRequest(user, provisioning);
if (ScimUserIdentifier.eduID.equals(provisioning.getScimUserIdentifier()) &&
request.getUserName().equals(user.getEduId())) {
//No fallback for failure
this.eduID.provisionEduid(new EduIDProvision(user.getEduId(), provisioning.getInstitutionGUID()));
}
String userRequest = prettyJson(request);
Optional<ProvisioningResponse> provisioningResponse = this.newRequest(provisioning, userRequest, user);
UserRequest userRequest = new UserRequest(user, provisioning);
this.resolveInstitutionalEduID(user, provisioning, userRequest);
String userRequestJson = prettyJson(userRequest);
Optional<ProvisioningResponse> provisioningResponse = this.newRequest(provisioning, userRequestJson, user);
provisioningResponse.ifPresent(response -> {
if (!response.isErrorResponse() && StringUtils.hasText(response.remoteIdentifier())) {
RemoteProvisionedUser remoteProvisionedUser = new RemoteProvisionedUser(user, response.remoteIdentifier(), provisioning.getId());
Expand All @@ -130,6 +126,17 @@ public Optional<GraphResponse> newUserRequest(User user) {
return Optional.ofNullable(graphResponseReference.get());
}

private void resolveInstitutionalEduID(User user, Provisioning provisioning, UserRequest request) {
if (ScimUserIdentifier.eduID.equals(provisioning.getScimUserIdentifier()) &&
request.getUserName().equals(user.getEduId())) {
//No fallback for failure
EduIDProvision eduIDProvision = new EduIDProvision(user.getEduId(), provisioning.getInstitutionGUID());
String eduIdProvisionResponse = this.eduID.provisionEduid(eduIDProvision);
//Empty eduIdProvisionResponse is handled by invite.eduid.EduID
request.setInsitutionalEduID(eduIdProvisionResponse);
}
}

@Override
public void updateUserRequest(User user) {
List<Provisioning> userProvisionings = getProvisionings(user);
Expand All @@ -144,8 +151,10 @@ public void updateUserRequest(User user) {
Optional<RemoteProvisionedUser> provisionedUserOptional =
this.remoteProvisionedUserRepository.findByManageProvisioningIdAndUser(provisioning.getId(), user);
provisionedUserOptional.ifPresent(remoteProvisionedUser -> {
String userRequest = prettyJson(new UserRequest(user, provisioning, remoteProvisionedUser.getRemoteIdentifier()));
this.updateRequest(provisioning, userRequest, APIType.USER_API, remoteProvisionedUser.getRemoteIdentifier(), HttpMethod.PUT);
UserRequest userRequest = new UserRequest(user, provisioning, remoteProvisionedUser.getRemoteIdentifier());
this.resolveInstitutionalEduID(user, provisioning, userRequest);
String userRequestJson = prettyJson(userRequest);
this.updateRequest(provisioning, userRequestJson, APIType.USER_API, remoteProvisionedUser.getRemoteIdentifier(), HttpMethod.PUT);
});
}
});
Expand Down Expand Up @@ -198,8 +207,10 @@ public void deleteUserRequest(User user) {
if (provisionedUserOptional.isPresent()) {
RemoteProvisionedUser remoteProvisionedUser = provisionedUserOptional.get();
String remoteIdentifier = remoteProvisionedUser.getRemoteIdentifier();
String userRequest = prettyJson(new UserRequest(user, provisioning, remoteIdentifier));
this.deleteRequest(provisioning, userRequest, user, remoteIdentifier);
UserRequest userRequest = new UserRequest(user, provisioning, remoteIdentifier);
this.resolveInstitutionalEduID(user, provisioning, userRequest);
String userRequestJson = prettyJson(userRequest);
this.deleteRequest(provisioning, userRequestJson, user, remoteIdentifier);
this.remoteProvisionedUserRepository.delete(remoteProvisionedUser);
}
});
Expand Down
10 changes: 8 additions & 2 deletions server/src/main/java/invite/provision/scim/UserRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public class UserRequest implements Serializable {
private static final Log LOG = LogFactory.getLog(UserRequest.class);

private final List<String> schemas = Collections.singletonList("urn:ietf:params:scim:schemas:core:2.0:User");
private final String externalId;
private final String userName;
private String externalId;
private String userName;
private final Name name;
private String id;
private final String displayName;
Expand All @@ -56,6 +56,12 @@ public UserRequest(User user, Provisioning provisioning, String remoteScimIdenti
this.id = remoteScimIdentifier;
}

public void setInsitutionalEduID(String eduIDValue) {
this.userName = eduIDValue;
this.externalId = eduIDValue;
}


private String resolveUserName(User user, Provisioning provisioning) {
ScimUserIdentifier scimUserIdentifier = provisioning.getScimUserIdentifier();
//Backward compatibility for older Provisionings without default
Expand Down
11 changes: 9 additions & 2 deletions server/src/test/java/invite/AbstractTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package invite;

import invite.config.HashGenerator;
import invite.eduid.EduIDProvision;
import invite.manage.EntityType;
import invite.manage.LocalManage;
import invite.model.*;
Expand Down Expand Up @@ -142,7 +143,7 @@ public abstract class AbstractTest {
protected LocalManage localManage;

@RegisterExtension
WireMockExtension mockServer = new WireMockExtension(8081);
protected WireMockExtension mockServer = new WireMockExtension(8081);

@LocalServerPort
protected int port;
Expand Down Expand Up @@ -345,14 +346,20 @@ protected JWTClaimsSet getJwtClaimsSet(String clientId, String sub, String redir
return builder.build();
}

protected void stubForProvisionEduID(String eduIdForInstitution) throws JsonProcessingException {
EduIDProvision eduIDProvision = new EduIDProvision(eduIdForInstitution, "semantically-not-used");
stubFor(post(urlPathMatching("/myconext/api/invite/provision-eduid")).willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(objectMapper.writeValueAsString(eduIDProvision))));
}

protected void stubForManageProvisioning(List<String> applicationIdentifiers) throws JsonProcessingException {
List<Map<String, Object>> providers = localManage.provisioning(applicationIdentifiers);
String body = writeValueAsString(providers);
stubFor(post(urlPathMatching("/manage/api/internal/provisioning"))
.willReturn(aResponse().withHeader("Content-Type", "application/json")
.withBody(body)
.withStatus(200)));

}

protected void stubForManageProvidersAllowedByIdP(String organisationGUID) throws JsonProcessingException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ void acceptForUpgradingExistingUserRole() throws Exception {
.findFirst().get().getAuthority();
assertEquals(Authority.GUEST, authority);

super.stubForProvisionEduID(UUID.randomUUID().toString());
//Because the user is changed and provisionings are queried
stubForManageProvisioning(List.of());
AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/login", GUEST_SUB);
Expand Down
1 change: 1 addition & 0 deletions server/src/test/java/invite/api/RoleControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ void updateApplications() throws Exception {

AccessCookieFilter accessCookieFilter = openIDConnectFlow("/api/v1/users/login", INSTITUTION_ADMIN_SUB);

super.stubForProvisionEduID(UUID.randomUUID().toString());
super.stubForManagerProvidersByIdIn(EntityType.SAML20_SP, List.of("1", "2", "4"));
super.stubForManageProvisioning(List.of("1", "2", "4"));
super.stubForCreateGraphUser();
Expand Down
1 change: 1 addition & 0 deletions server/src/test/java/invite/api/UserControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,7 @@ void meUpdateScim() throws Exception {
remoteProvisionedUserRepository.save(remoteProvisionedUser);

super.stubForManageProvisioning(List.of("1", "4", "5"));
super.stubForProvisionEduID(UUID.randomUUID().toString());
super.stubForUpdateScimUser();

//This will trigger the SCIM update request, see CustomOidcUserService#loadUser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ private void doUserRoleProvisioning(UserRoleProvisioning userRoleProvisioning, S
super.stubForManagerProvidersByIdIn(EntityType.SAML20_SP, List.of("1", "2"));
super.stubForManageProvidersAllowedByIdP(ORGANISATION_GUID);
super.stubForManageProvisioning(List.of("1", "2"));

super.stubForProvisionEduID(UUID.randomUUID().toString());
super.stubForCreateScimUser();
super.stubForCreateGraphUser();
super.stubForCreateScimRole();
Expand Down
11 changes: 5 additions & 6 deletions server/src/test/java/invite/eduid/EduIDTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import invite.WireMockExtension;
import com.fasterxml.jackson.databind.ObjectMapper;
import invite.exception.RemoteException;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
Expand All @@ -10,8 +11,7 @@
import java.util.UUID;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;

class EduIDTest {

Expand All @@ -30,8 +30,8 @@ void provisionEduid() {
stubFor(post(urlPathMatching("/myconext/api/invite/provision-eduid")).willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(objectMapper.writeValueAsString(eduIDProvision))));
Optional<String> eduid = eduID.provisionEduid(eduIDProvision);
assertEquals(eduIDProvision.getEduIDValue(), eduid.get());
String eduid = eduID.provisionEduid(eduIDProvision);
assertEquals(eduIDProvision.getEduIDValue(), eduid);
}

@SneakyThrows
Expand All @@ -41,7 +41,6 @@ void provisionEduid404() {
stubFor(post(urlPathMatching("/myconext/api/invite/provision-eduid")).willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withStatus(404)));
Optional<String> eduid = eduID.provisionEduid(eduIDProvision);
assertTrue(eduid.isEmpty());
assertThrows(RemoteException.class, ()-> eduID.provisionEduid(eduIDProvision)) ;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package invite.provision;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.github.tomakehurst.wiremock.stubbing.ServeEvent;
import com.github.tomakehurst.wiremock.verification.LoggedRequest;
import invite.AbstractTest;
import invite.eduid.EduIDProvision;
import invite.exception.RemoteException;
import invite.model.*;
import invite.model.Authority;
import invite.model.RemoteProvisionedGroup;
import invite.model.RemoteProvisionedUser;
import invite.model.Role;
import invite.model.User;
import invite.model.UserRole;
import invite.provision.scim.OperationType;
import invite.provision.scim.UserRequest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;

import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -49,19 +54,28 @@ void newUserRequestWithInvalidRemoteResponse() throws JsonProcessingException {
@Test
void newUserRequestWithEduIDProvisioning() throws JsonProcessingException {
User user = userRepository.findBySubIgnoreCase(GUEST_SUB).get();

this.stubForManageProvisioning(List.of("1"));

EduIDProvision eduIDProvision = new EduIDProvision(user.getEduId(), UUID.randomUUID().toString());
stubFor(post(urlPathMatching("/myconext/api/invite/provision-eduid")).willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(objectMapper.writeValueAsString(eduIDProvision))));
String eduIdForInstitution = UUID.randomUUID().toString();
super.stubForProvisionEduID(eduIdForInstitution);

String remoteScimIdentifier = this.stubForCreateScimUser();
provisioningService.newUserRequest(user);
List<ServeEvent> events = this.mockServer.getAllServeEvents();
ServeEvent serveEvent = events.stream()
.filter(event -> event.getRequest().getUrl().equalsIgnoreCase("/api/scim/v2/Users"))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
//UserRequest can not be deserialized from String due to missing constructors and setters
Map<String, Object> userRequest = objectMapper.readValue(serveEvent.getRequest().getBodyAsString(), new TypeReference<>() {
});
assertEquals(eduIdForInstitution, userRequest.get("userName"));
assertEquals(eduIdForInstitution, userRequest.get("externalId"));

List<RemoteProvisionedUser> remoteProvisionedUsers = remoteProvisionedUserRepository.findAll();
assertEquals(1, remoteProvisionedUsers.size());
assertEquals(remoteScimIdentifier, remoteProvisionedUsers.get(0).getRemoteIdentifier());

}

@Test
Expand Down Expand Up @@ -93,6 +107,7 @@ void updateUserRequest() throws JsonProcessingException {
remoteProvisionedUserRepository.save(remoteProvisionedUser);
this.stubForManageProvisioning(List.of("1", "4", "5"));
this.stubForUpdateScimUser();
super.stubForProvisionEduID(UUID.randomUUID().toString());
provisioningService.updateUserRequest(user);
List<LoggedRequest> loggedRequests = findAll(putRequestedFor(urlPathMatching(String.format("/api/scim/v2/Users/(.*)"))));

Expand All @@ -101,6 +116,26 @@ void updateUserRequest() throws JsonProcessingException {
assertEquals(remoteScimIdentifier, userRequest.get("id"));
}

@Test
void updateUserRequestWithEduIDProvisioningError() throws JsonProcessingException {
User user = userRepository.findBySubIgnoreCase(GUEST_SUB).get();
//Need to ensure the user is updated, therefore the remote needs to exists and provisioning is scimn
String remoteScimIdentifier = UUID.randomUUID().toString();
RemoteProvisionedUser remoteProvisionedUser = new RemoteProvisionedUser(user, remoteScimIdentifier, "7");
remoteProvisionedUserRepository.save(remoteProvisionedUser);
this.stubForManageProvisioning(List.of("1", "4", "5"));
this.stubForUpdateScimUser();
try {
provisioningService.updateUserRequest(user);
fail("Expected RemoteException");
} catch (RemoteException e) {
assertTrue(e.getMessage().contains("Error in provisionEduid"));
assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode());
assertNotNull(e.getReference());
}

}

@Test
void deleteUserRequest() throws JsonProcessingException {
User user = userRepository.findBySubIgnoreCase(GUEST_SUB).get();
Expand Down
Loading