Skip to content

Commit

Permalink
WIP for storing applications
Browse files Browse the repository at this point in the history
  • Loading branch information
oharsta committed Nov 23, 2023
1 parent 65509b2 commit 947511d
Show file tree
Hide file tree
Showing 20 changed files with 185 additions and 140 deletions.
5 changes: 5 additions & 0 deletions server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-60</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
Expand Down
21 changes: 15 additions & 6 deletions server/src/main/java/access/api/ManageController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import access.config.Config;
import access.manage.EntityType;
import access.manage.Manage;
import access.manage.ManageIdentifier;
import access.model.Authority;
import access.model.User;
import access.repository.RoleRepository;
Expand All @@ -21,7 +22,10 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static access.SwaggerOpenIdConfig.API_TOKENS_SCHEME_NAME;
Expand Down Expand Up @@ -72,15 +76,20 @@ public ResponseEntity<List<Map<String, Object>>> providers(@Parameter(hidden = t
@GetMapping("applications")
public ResponseEntity<Map<String, List<Map<String, Object>>>> applications(@Parameter(hidden = true) User user) {
UserPermissions.assertSuperUser(user);
List<String[]> manageIdentifiers = roleRepository.findDistinctManageIdentifiers();
Map<String, List<String[]>> groupedByManageType = manageIdentifiers.stream().collect(Collectors.groupingBy(s -> s[0]));
List<ManageIdentifier> manageIdentifiers = roleRepository.findDistinctManageIdentifiers().stream()
.map(tuple -> new ManageIdentifier(tuple[0].replaceAll("[\"\\]\\[]", ""),
EntityType.valueOf(tuple[1].replaceAll("[\"\\]\\[]", ""))))
.toList();
Map<EntityType, List<ManageIdentifier>> groupedByManageType = manageIdentifiers.stream().collect(Collectors.groupingBy(ManageIdentifier::entityType));
List<Map<String, Object>> providers = groupedByManageType.entrySet().stream()
.map(entry -> manage.providersByIdIn(
EntityType.valueOf(entry.getKey()),
entry.getValue().stream().map(s -> s[1]).collect(Collectors.toList())))
entry.getKey(),
entry.getValue().stream().map(ManageIdentifier::id).collect(Collectors.toList())))
.flatMap(Collection::stream)
.toList();
List<Map<String, Object>> provisionings = manage.provisioning(manageIdentifiers.stream().map(s -> s[1]).toList());
List<Map<String, Object>> provisionings = manage.provisioning(manageIdentifiers.stream()
.map(ManageIdentifier::id)
.toList());
return ResponseEntity.ok(Map.of(
"providers", providers,
"provisionings", provisionings
Expand Down
8 changes: 5 additions & 3 deletions server/src/main/java/access/api/RoleController.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,18 @@ public ResponseEntity<List<Role>> rolesByApplication(@Parameter(hidden = true) U
UserPermissions.assertAuthority(user, Authority.MANAGER);
List<Role> roles = new ArrayList<>();
if (user.isInstitutionAdmin()) {
roles.addAll(roleRepository.findByApplicationsManageIdIn(user.getApplications().stream().map(m -> (String) m.get("id")).collect(Collectors.toSet())));
Set<String> manageIdentifiers = user.getApplications().stream().map(m -> (String) m.get("id")).collect(Collectors.toSet());
//This is a shortcoming of the json_array
manageIdentifiers.forEach(manageId -> roles.addAll(roleRepository.findByApplicationsManageId(manageId)));
}
Set<String> manageIdentifiers = user.getUserRoles().stream()
//If the user has an userRole as Inviter, then we must exclude those
.filter(userRole -> userRole.getAuthority().hasEqualOrHigherRights(Authority.MANAGER))
.map(userRole -> userRole.getRole().getApplications())
.flatMap(Collection::stream)
.map(application -> application.getManageId())
.map(Application::getManageId)
.collect(Collectors.toSet());
roles.addAll(roleRepository.findByApplicationsManageIdIn(manageIdentifiers));
manageIdentifiers.forEach(manageId -> roles.addAll(roleRepository.findByApplicationsManageId(manageId)));
return ResponseEntity.ok(manage.addManageMetaData(roles));
}

Expand Down
30 changes: 30 additions & 0 deletions server/src/main/java/access/config/ApplicationConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package access.config;

import access.manage.EntityType;
import access.model.Application;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.AttributeConverter;
import lombok.SneakyThrows;

import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class ApplicationConverter implements AttributeConverter<Set<Application>, String> {

private final static ObjectMapper objectMapper = new ObjectMapper();

@SneakyThrows
@Override
public String convertToDatabaseColumn(Set<Application> attribute) {
return objectMapper.writeValueAsString(attribute);
}

@SneakyThrows
@Override
@SuppressWarnings("unchecked")
public Set<Application> convertToEntityAttribute(String dbData) {
Set<Map<String, String>> set = objectMapper.readValue(dbData, Set.class);
return set.stream().map(m -> new Application(m.get("manageId"), EntityType.valueOf(m.get("manageType")))).collect(Collectors.toSet());
}
}

This file was deleted.

22 changes: 3 additions & 19 deletions server/src/main/java/access/model/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import access.manage.EntityType;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.*;

import java.io.Serializable;
import java.time.Instant;
Expand All @@ -15,28 +13,14 @@
import java.io.Serializable;
import java.util.Set;

@Entity(name = "applications")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@EqualsAndHashCode
public class Application implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "manage_id")
private String manageId;

@Column(name = "manage_type")
@Enumerated(EnumType.STRING)
private EntityType manageType;

@ManyToMany(mappedBy = "applications")
private Set<Role> roles;

public Application(String manageId, EntityType manageType) {
this.manageId = manageId;
this.manageType = manageType;
}
}
12 changes: 12 additions & 0 deletions server/src/main/java/access/model/DistinctManagerIdentifiers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package access.model;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class DistinctManagerIdentifiers {

private String identifiers;

}
12 changes: 7 additions & 5 deletions server/src/main/java/access/model/Role.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package access.model;


import access.config.ApplicationConverter;
import access.provision.scim.GroupURN;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import org.hibernate.annotations.Formula;

import java.io.Serializable;
Expand Down Expand Up @@ -58,11 +62,8 @@ public class Role implements Serializable, Provisionable {
@Formula(value = "(SELECT COUNT(*) FROM user_roles ur WHERE ur.role_id=id)")
private Long userRoleCount;

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(
name = "roles_applications",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "application_id"))
@Column
@Convert(converter = ApplicationConverter.class)
private Set<Application> applications = new HashSet<>();

@OneToMany(mappedBy = "role",
Expand Down Expand Up @@ -107,6 +108,7 @@ public Role(@NotNull String name,
this.eduIDOnly = eduIDOnly;
this.applications = applications;
this.applicationMaps = applicationMaps;
this.identifier = UUID.randomUUID().toString();
}

@Transient
Expand Down
12 changes: 10 additions & 2 deletions server/src/main/java/access/repository/RoleRepository.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package access.repository;

import access.model.DistinctManagerIdentifiers;
import access.model.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -17,11 +18,18 @@ public interface RoleRepository extends JpaRepository<Role, Long> {
nativeQuery = true)
List<Role> search(String keyWord, int limit);

List<Role> findByApplicationsManageIdIn(Set<String> manageIdentifiers);

@Query(value = "SELECT DISTINCT manage_type, manage_id FROM applications", nativeQuery = true)
@Query(value = "SELECT *, (SELECT COUNT(*) FROM user_roles ur WHERE ur.role_id=r.id) AS userRoleCount " +
"FROM roles r WHERE json_contains(applications->'$[*].manageId', json_array(?1))",
nativeQuery = true)
List<Role> findByApplicationsManageId(String manageId);

@Query(value = "SELECT DISTINCT JSON_EXTRACT(applications,'$[*].manageId','$[0].manageType') FROM roles", nativeQuery = true)
List<String[]> findDistinctManageIdentifiers();

@Query(value = "SELECT *, (SELECT COUNT(*) FROM user_roles ur WHERE ur.role_id=r.id) as userRoleCount " +
"FROM roles r WHERE json_contains(applications->'$[*].manageId', json_array(?1)) and short_name = ?2",
nativeQuery = true)
Optional<Role> findByShortNameIgnoreCaseAndApplicationsManageId(String managerId, String name);

List<Role> findByName(String name);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package db.mysql.migration;

import access.manage.EntityType;
import access.model.Application;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;

import java.util.Set;
import java.util.UUID;


public class V11_0__migrate_applications extends BaseJavaMigration {

public void migrate(Context context) {
JdbcTemplate jdbcTemplate =
new JdbcTemplate(new SingleConnectionDataSource(context.getConnection(), true));
ObjectMapper objectMapper = new ObjectMapper();

jdbcTemplate.query("SELECT id, manage_id, manage_type FROM roles", rs -> {
long roleId = rs.getLong("id");
jdbcTemplate.update("UPDATE roles SET identifier = ? WHERE id = ?", UUID.randomUUID().toString(), roleId);

String manageId = rs.getString("manage_id");
String manageType = rs.getString("manage_type");
Set<Application> applications = Set.of(new Application(manageId, EntityType.valueOf(manageType)));
String jsonNode;
try {
jsonNode = objectMapper.writeValueAsString(applications);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
jdbcTemplate.update("UPDATE roles SET applications = ?", jsonNode);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,2 @@
CREATE TABLE `applications`
(
`id` bigint NOT NULL AUTO_INCREMENT,
`manage_id` varchar(255) NOT NULL,
`manage_type` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `applications_unique_manage` (`manage_id`,`manage_type`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4;

CREATE TABLE `roles_applications`
(
`id` bigint NOT NULL AUTO_INCREMENT,
`role_id` bigint NOT NULL,
`application_id` bigint NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_roles_unique_user_role` (`user_id`, `role_id`),
CONSTRAINT `fk_roles_applications_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_roles_applications_application` FOREIGN KEY (`application_id`) REFERENCES `applications` (`id`) ON DELETE CASCADE
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4;

ALTER TABLE `roles` add `identifier` varchar(255) DEFAULT NULL;
ALTER TABLE `roles` add `identifier` varchar(255) DEFAULT NULL;
ALTER TABLE `roles` add `applications` json DEFAULT NULL;
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
ALTER TABLE `roles` MODIFY `identifier` varchar(255) NOT NULL;
ALTER TABLE `roles` MODIFY `applications` json NOT NULL;
ALTER TABLE `roles` DROP COLUMN `manage_id`;
ALTER TABLE `roles` DROP COLUMN `manage_type`;
11 changes: 11 additions & 0 deletions server/src/test/java/access/AbstractTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.restassured.filter.cookie.CookieFilter;
import io.restassured.http.ContentType;
import io.restassured.http.Headers;
import lombok.SneakyThrows;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -357,6 +358,16 @@ protected void stubForManageProviderById(EntityType entityType, String id) throw
.withBody(body)));
}

@SneakyThrows
protected void stubForManagerProvidersByIdIn(EntityType entityType, List<String> identifiers) {
String path = String.format("/manage/api/internal/rawSearch/%s\\\\?.*", entityType.name().toLowerCase());
List<Map<String, Object>> providers = localManage.providersByIdIn(entityType, identifiers);
String body = objectMapper.writeValueAsString(providers);
stubFor(get(urlPathMatching(path)).willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(body)));
}

protected void stubForManageProviderByEntityID(EntityType entityType, String entityId) throws JsonProcessingException {
String path = String.format("/manage/api/internal/rawSearch/%s\\\\?.*", entityType.name().toLowerCase());
Optional<Map<String, Object>> provider = localManage.providerByEntityID(entityType, entityId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import static access.Seed.GUEST_SUB;
import static io.restassured.RestAssured.given;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class AttributeAggregatorControllerTest extends AbstractTest {

Expand All @@ -30,7 +31,7 @@ void getGroupMemberships() throws JsonProcessingException {
.as(new TypeRef<>() {
});
assertEquals(1, roles.size());
assertEquals("urn:mace:surf.nl:test.surfaccess.nl:4:research", roles.get(0).get("id"));
assertTrue(roles.get(0).get("id").startsWith("urn:mace:surf.nl:test.surfaccess.nl:"));
}

@Test
Expand Down
Loading

0 comments on commit 947511d

Please sign in to comment.