Skip to content

Commit

Permalink
UserRole API for institution admin
Browse files Browse the repository at this point in the history
  • Loading branch information
oharsta committed Dec 1, 2023
1 parent 39e53b5 commit 5bf69cf
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 13 deletions.
9 changes: 5 additions & 4 deletions server/src/main/java/access/api/UserRoleController.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

import static access.SwaggerOpenIdConfig.API_TOKENS_SCHEME_NAME;
import static access.SwaggerOpenIdConfig.OPEN_ID_SCHEME_NAME;

import io.swagger.v3.oas.annotations.Operation;
@RestController
@RequestMapping(value = {"/api/v1/user_roles", "/api/external/v1/user_roles"}, produces = MediaType.APPLICATION_JSON_VALUE)
@Transactional
Expand Down Expand Up @@ -72,7 +72,8 @@ public ResponseEntity<List<UserRole>> byRole(@PathVariable("roleId") Long roleId
}

@PostMapping("user_role_provisioning")
public ResponseEntity<Map<String, Integer>> userRoleProvisioning(@Validated @RequestBody UserRoleProvisioning userRoleProvisioning,
@Operation(summary = "Add Role to a User", description = "Provision the User if the User is unknown and add the Role(s)")
public ResponseEntity<User> userRoleProvisioning(@Validated @RequestBody UserRoleProvisioning userRoleProvisioning,
@Parameter(hidden = true) User apiUser) {
userRoleProvisioning.validate();
UserPermissions.assertInstitutionAdmin(apiUser);
Expand All @@ -89,7 +90,7 @@ public ResponseEntity<Map<String, Integer>> userRoleProvisioning(@Validated @Req
} else if (StringUtils.hasText(userRoleProvisioning.email)) {
userOptional = userRepository.findByEmailIgnoreCase(userRoleProvisioning.email);
}
//Can't use shorthand notation as there are probably null values
//Provision user if not found - minimal requirement is an email
User user = userOptional.orElseGet(() -> userRepository.save(new User(userRoleProvisioning)));

List<UserRole> newUserRoles = roles.stream()
Expand All @@ -112,7 +113,7 @@ public ResponseEntity<Map<String, Integer>> userRoleProvisioning(@Validated @Req
provisioningService.newUserRequest(user);
newUserRoles.forEach(userRole -> provisioningService.updateGroupRequest(userRole, OperationType.Add));

return Results.createResult();
return ResponseEntity.status(201).body(user);
}


Expand Down
12 changes: 8 additions & 4 deletions server/src/main/java/access/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,11 @@ public User(boolean superUser, Map<String, Object> attributes) {
}

public User(UserRoleProvisioning userRoleProvisioning) {
this.sub = resolveSub(userRoleProvisioning);
this.eduPersonPrincipalName = userRoleProvisioning.eduPersonPrincipalName;
this.schacHomeOrganization = userRoleProvisioning.schacHomeOrganization;
userRoleProvisioning.validate();
this.sub = userRoleProvisioning.resolveSub();
this.email = userRoleProvisioning.email;
this.eduPersonPrincipalName = StringUtils.hasText(userRoleProvisioning.eduPersonPrincipalName) ? userRoleProvisioning.eduPersonPrincipalName : this.email;
this.schacHomeOrganization = StringUtils.hasText(userRoleProvisioning.schacHomeOrganization) ? userRoleProvisioning.schacHomeOrganization : this.schacHomeOrganization;
this.name = userRoleProvisioning.name;
this.givenName = userRoleProvisioning.givenName;
this.familyName = userRoleProvisioning.familyName;
Expand Down Expand Up @@ -220,7 +221,10 @@ public Optional<UserRole> latestUserRole() {
}

@JsonIgnore
public String resolveSub(UserRoleProvisioning userRoleProvisioning) {
private static String resolveSub(UserRoleProvisioning userRoleProvisioning) {
if (StringUtils.hasText(userRoleProvisioning.sub)) {
return userRoleProvisioning.sub;
}
String schacHome = null;
String uid = null;
if (StringUtils.hasText(userRoleProvisioning.schacHomeOrganization)) {
Expand Down
35 changes: 34 additions & 1 deletion server/src/main/java/access/model/UserRoleProvisioning.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package access.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.ToString;
Expand All @@ -17,6 +19,7 @@ public class UserRoleProvisioning {
public List<Long> roleIdentifiers;
public Authority intendedAuthority = Authority.GUEST;
public String sub;
@NotNull
public String email;
public String eduPersonPrincipalName;
public String givenName;
Expand All @@ -25,9 +28,39 @@ public class UserRoleProvisioning {
public String schacHomeOrganization;

public void validate() {
if (!StringUtils.hasText(email) && !StringUtils.hasText(eduPersonPrincipalName)) {
if (!StringUtils.hasText(email) && (!StringUtils.hasText(eduPersonPrincipalName) || !eduPersonPrincipalName.contains("@"))) {
throw new IllegalArgumentException("Requires one off: email, eduPersonPrincipalName. Invalid userRoleProvisioning: " + this);
}
}

@JsonIgnore
public String resolveSub() {
if (StringUtils.hasText(sub)) {
return sub;
}
String schacHome = null;
String uid = null;
if (StringUtils.hasText(schacHomeOrganization)) {
schacHome = schacHomeOrganization;
}
String eppn = eduPersonPrincipalName;
if (StringUtils.hasText(eppn) && eppn.contains("@")) {
uid = eppn.substring(0, eppn.indexOf("@"));
schacHome = schacHome != null ? schacHome : eppn.substring(eppn.indexOf("@") + 1);
}
String mail = email;
if (StringUtils.hasText(mail)) {
uid = uid != null ? uid : mail.substring(0, mail.indexOf("@"));
schacHome = schacHome != null ? schacHome : mail.substring(mail.indexOf("@") + 1);
}
if (schacHome == null || uid == null) {
throw new IllegalArgumentException("Can't resolve sub from " + this);
}
if (!StringUtils.hasText(this.schacHomeOrganization) && StringUtils.hasText(schacHome)) {
this.schacHomeOrganization = schacHome;
}
return String.format("urn:collab:person:%s:%s", schacHome, uid);
}


}
8 changes: 4 additions & 4 deletions server/src/test/java/access/api/UserRoleControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,16 @@ void userRoleProvisioning() throws Exception {
"Charly Green",
null
);
given()
User savedUser = given()
.when()
.header(API_TOKEN_HEADER, API_TOKEN_HASH)
.accept(ContentType.JSON)
.contentType(ContentType.JSON)
.body(userRoleProvisioning)
.post("/api/external/v1/user_roles/user_role_provisioning")
.then()
.statusCode(201);

.as(new TypeRef<>() {
});
assertEquals("urn:collab:person:domain.org:new_user", savedUser.getSub());
User user = userRepository.findBySubIgnoreCase("urn:collab:person:domain.org:new_user").orElseThrow(NotFoundException::new);
assertEquals(2, user.getUserRoles().size());
}
Expand Down
89 changes: 89 additions & 0 deletions server/src/test/java/access/model/UserRoleProvisioningTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package access.model;

import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class UserRoleProvisioningTest {

@Test
void resolveSubWithEmail() {
User user = new User(new UserRoleProvisioning(
List.of(1L),
Authority.GUEST,
null,
"[email protected]",
null,
null,
null,
null,
null
));
assertEquals("urn:collab:person:example.com:jdoe", user.getSub());
assertEquals("example.com", user.getSchacHomeOrganization());
}

@Test
void resolveSubWithEPPN() {
User user = new User(new UserRoleProvisioning(
List.of(1L),
Authority.GUEST,
null,
null,
"[email protected]",
null,
null,
null,
null
));
assertEquals("urn:collab:person:example.com:jdoe", user.getSub());
assertEquals("example.com", user.getSchacHomeOrganization());
}

@Test
void resolveSubWithSub() {
User user = new User(new UserRoleProvisioning(
List.of(1L),
Authority.GUEST,
"urn:collab:person:example.com:jdoe",
"[email protected]",
null,
null,
null,
null,
"nice.org"
));
assertEquals("urn:collab:person:example.com:jdoe", user.getSub());
assertEquals("nice.org", user.getSchacHomeOrganization());
}

@Test
void resolveSubWithInvalidUserRoleProvisioning() {
assertThrows(IllegalArgumentException.class, () -> new User(new UserRoleProvisioning(
List.of(1L),
Authority.GUEST,
null,
null,
"nope",
null,
null,
null,
null
)));
assertThrows(IllegalArgumentException.class, () -> new User(new UserRoleProvisioning(
List.of(1L),
Authority.GUEST,
null,
null,
null,
null,
null,
null,
null
)));
}

}
1 change: 1 addition & 0 deletions server/src/test/java/access/model/UserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;
Expand Down

0 comments on commit 5bf69cf

Please sign in to comment.