This commit is contained in:
Timo Bejan 2023-02-28 20:39:25 +02:00
parent 94061a38de
commit 72b0a3d933
827 changed files with 25597 additions and 9021 deletions

View File

@ -1,7 +1,18 @@
version: '2'
services:
keycloak:
image: quay.io/keycloak/keycloak:20.0
command: start-dev
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
ports:
- 8080:8080
redis:
image: redis
ports:
- "6379:6379"
db:
image: postgres
restart: always
@ -10,7 +21,7 @@ services:
environment:
POSTGRES_USER: redaction
POSTGRES_PASSWORD: redaction
POSTGRES_DB: redaction
POSTGRES_DB: master
rabbitmq:
image: 'rabbitmq:3.9-alpine'
mem_limit: 500m

View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>persistence-service-v1</artifactId>
<groupId>com.iqser.red.service</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-commons</artifactId>
<dependencies>
<dependency>
<groupId>com.iqser.red.commons</groupId>
<artifactId>spring-commons</artifactId>
</dependency>
<dependency>
<groupId>com.iqser.red.commons</groupId>
<artifactId>logging-commons</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>com.iqser.red.commons</groupId>
<artifactId>metric-commons</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-adapter-core</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-security-adapter</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.13.4</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- spring -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.iqser.red.commons</groupId>
<artifactId>jackson-commons</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>com.iqser.red.commons</groupId>
<artifactId>test-commons</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,24 @@
package com.iqser.red.keycloak.commons;
import static com.iqser.red.keycloak.commons.UserCacheBuilder.USERS_CACHE;
import java.time.Duration;
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
@Configuration
@ComponentScan
public class DefaultKeyCloakCommonsConfiguration {
@Bean
public RedisCacheManagerBuilderCustomizer redisUserCacheManagerBuilderCustomizer() {
return (builder) -> builder.withCacheConfiguration(USERS_CACHE, RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(10)));
}
}

View File

@ -0,0 +1,41 @@
package com.iqser.red.keycloak.commons;
import java.util.concurrent.TimeUnit;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.internal.ResteasyClientBuilderImpl;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.springframework.stereotype.Service;
@Service
public class KeyCloakAdminClientService {
private final Keycloak adminClient;
public KeyCloakAdminClientService(KeyCloakSettings settings) {
adminClient = KeycloakBuilder.builder()
.serverUrl(settings.getServerUrl())
.realm(settings.getRealm())
.clientId(settings.getClientId())
.clientSecret(settings.getClientSecret())
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.resteasyClient(new ResteasyClientBuilderImpl().connectionTTL(2, TimeUnit.SECONDS)
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
.connectionPoolSize(settings.getConnectionPoolSize())
.disableTrustManager()
.build())
.build();
}
public Keycloak getAdminClient() {
return adminClient;
}
}

View File

@ -0,0 +1,135 @@
package com.iqser.red.keycloak.commons;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.GET_RSS;
import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_ADMIN_ROLE;
import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_MANAGER_ROLE;
import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_USER_ADMIN_ROLE;
import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_USER_ROLE;
import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.UNMAPPED_ACTION_ROLES;
import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Collectors;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.iqser.red.keycloak.commons.roles.ApplicationRoles;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@RequiredArgsConstructor
public class KeyCloakRoleManager implements ApplicationRunner {
private final RealmService realmService;
private final KeyCloakSettings settings;
@Override
public void run(ApplicationArguments args) {
log.info("Running KeyCloak Role Manager, managing client: {} with system client {}", settings.getApplicationClientId(), settings.getClientId());
var existingRoles = realmService.realm().roles().list().stream().map(RoleRepresentation::getName).collect(Collectors.toList());
log.info("Existing KC roles: {}", existingRoles);
var redactionClientRepresentation = getRedactionClientRepresentation();
var redactionClient = realmService.realm().clients().get(redactionClientRepresentation.getId());
var clientRoles = redactionClient.roles().list().stream().map(RoleRepresentation::getName).collect(Collectors.toList());
var allRoles = ApplicationRoles.ROLE_DATA.values().stream().flatMap(Set::stream).collect(Collectors.toSet());
allRoles.addAll(UNMAPPED_ACTION_ROLES);
log.info("Existing KC client roles: {}", clientRoles);
log.info("Current Application KC client roles: {}", allRoles);
if (!Sets.newHashSet(clientRoles).equals(allRoles)) {
log.info("Role-Sets are different, recreating form scratch ... ");
// remove all roles from the redaction client
clientRoles.forEach(clientRole -> {
try {
redactionClient.roles().deleteRole(clientRole);
}catch (Exception e){
log.warn("Failed to delete client role: {}",clientRole);
}
});
// re-create all client-roles
allRoles.forEach(role -> {
var roleRepresentation = new RoleRepresentation(role, role, false);
redactionClient.roles().create(roleRepresentation);
});
log.info("Cleaned up KC client roles and written current ones!");
}
var allClientRoles = redactionClient.roles().list();
if (settings.isScmEnabled()) {
ApplicationRoles.ROLE_DATA.get(RED_USER_ROLE).add(GET_RSS);
}
// if an application-role doesn't exist, create it
for (String applicationRole : ApplicationRoles.ROLE_DATA.keySet()) {
log.info("Running Role Composition for role: {}", applicationRole);
if (!existingRoles.contains(applicationRole)) {
log.info("Application Role: {} doesn't exist, creating it now", applicationRole);
var role = new RoleRepresentation(applicationRole, applicationRole, false);
role.setComposite(true);
realmService.realm().roles().create(role);
}
var applicationRoleResource = realmService.realm().roles().get(applicationRole);
Set<RoleRepresentation> composites = realmService.realm()
.rolesById()
.getClientRoleComposites(applicationRoleResource.toRepresentation().getId(), redactionClient.toRepresentation().getId());
log.info("Deleting previous composites for application role {}", applicationRole);
realmService.realm().rolesById().deleteComposites(applicationRoleResource.toRepresentation().getId(), new ArrayList<>(composites));
var relevantClientRoles = allClientRoles.stream().filter(role -> ApplicationRoles.ROLE_DATA.get(applicationRole).contains(role.getName())).collect(Collectors.toList());
log.info("Writing new composites for application role {}", applicationRole);
realmService.realm().rolesById().addComposites(applicationRoleResource.toRepresentation().getId(), relevantClientRoles);
log.info("Finished application role {}", applicationRole);
}
// add RED_USER Realm Role to RED_MANAGER
var redUserRole = realmService.realm().roles().get(RED_USER_ROLE);
var redManagerRole = realmService.realm().roles().get(RED_MANAGER_ROLE);
realmService.realm().rolesById().addComposites(redManagerRole.toRepresentation().getId(), Lists.newArrayList(redUserRole.toRepresentation()));
// add RED_USER_ADMIN Realm Role to RED_ADMIN
var redAdminRole = realmService.realm().roles().get(RED_ADMIN_ROLE);
var redUserAdminRole = realmService.realm().roles().get(RED_USER_ADMIN_ROLE);
realmService.realm().rolesById().addComposites(redAdminRole.toRepresentation().getId(), Lists.newArrayList(redUserAdminRole.toRepresentation()));
log.info("Finished KC Role Manager");
}
private ClientRepresentation getRedactionClientRepresentation() {
String applicationClientId = settings.getApplicationClientId();
var clientRepresentationIterator = realmService.realm().clients().findByClientId(applicationClientId).iterator();
if (clientRepresentationIterator.hasNext()) {
return clientRepresentationIterator.next();
} else {
throw new IllegalStateException(String.format("The application client information for the id %s could not be retrieved. " + //
"Please check the application settings and correct the license-service/userKeycloakSettings/applicationClientId setting.", applicationClientId));
}
}
}

View File

@ -0,0 +1,21 @@
package com.iqser.red.keycloak.commons;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
@Data
@ConfigurationProperties("commons.keycloak")
public class KeyCloakSettings {
private String serverUrl;
private String realm;
private String applicationClientId;
private String clientId;
private String clientSecret;
private String issuer;
private String rolePrefix = "RED_";
private int connectionPoolSize = 10;
private boolean scmEnabled;
}

View File

@ -0,0 +1,30 @@
package com.iqser.red.keycloak.commons;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
/**
* Collection of helpful functions to easily access information about an authenticated user.
*/
@Slf4j
@UtilityClass
public class KeycloakSecurity {
/**
* Determines the unique identifier for the currently logged in user.
*
* @return The unique user identifier. Never {@code null}.
*/
public String getUserId() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null) {
return "anonymousUser";
}
return auth.getName();
}
}

View File

@ -0,0 +1,18 @@
package com.iqser.red.keycloak.commons;
import org.springframework.stereotype.Service;
@Service
public class KeycloakSecurityService {
/**
* Determines the unique identifier for the currently logged in user.
*
* @return The unique user identifier. Never {@code null}.
*/
public String getUserId() {
return KeycloakSecurity.getUserId();
}
}

View File

@ -0,0 +1,24 @@
package com.iqser.red.keycloak.commons;
import org.keycloak.admin.client.resource.RealmResource;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Service
@RequiredArgsConstructor
@Slf4j
public class RealmService {
private final KeyCloakAdminClientService keycloak;
private final KeyCloakSettings settings;
public RealmResource realm() {
return keycloak.getAdminClient().realm(settings.getRealm());
}
}

View File

@ -0,0 +1,26 @@
package com.iqser.red.keycloak.commons;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class UserCacheBuilder {
public static final String USERS_CACHE = "users";
private final UserListingService userService;
@PostConstruct
protected void postConstruct() {
userService.getAllUsers();
}
}

View File

@ -0,0 +1,99 @@
package com.iqser.red.keycloak.commons;
import static com.iqser.red.keycloak.commons.UserCacheBuilder.USERS_CACHE;
import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_ADMIN_ROLE;
import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_MANAGER_ROLE;
import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_USER_ADMIN_ROLE;
import static com.iqser.red.keycloak.commons.roles.ApplicationRoles.RED_USER_ROLE;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.keycloak.representations.idm.UserRepresentation;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;
import com.iqser.red.keycloak.commons.model.User;
import com.iqser.red.keycloak.commons.roles.ApplicationRoles;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class UserListingService {
private final RealmService realmService;
private final RetryTemplate retryTemplate = RetryTemplate.builder().maxAttempts(3).exponentialBackoff(1000, 2, 5000).build();
@Cacheable(value = USERS_CACHE)
public List<User> getAllUsers() {
return retryTemplate.execute(context -> {
List<UserRepresentation> allUsers = realmService.realm().users().search(null, 0, 500);
Map<String, Set<String>> usersByRole = new HashMap<>();
if(!allUsers.isEmpty()) {
var realmRoles = realmService.realm().roles().list().stream().map(r -> r.getName().toUpperCase()).collect(Collectors.toSet());
for (var role : ApplicationRoles.ROLE_DATA.keySet()) {
if(realmRoles.contains(role)) {
Set<UserRepresentation> users = realmService.realm().roles().get(role).getRoleUserMembers(0, 500);
usersByRole.put(role, users.stream().map(UserRepresentation::getId).collect(Collectors.toSet()));
}
}
}
return compactUsers(allUsers, usersByRole);
});
}
private List<User> compactUsers(List<UserRepresentation> allUsers, Map<String, Set<String>> usersByRole) {
List<User> users = new ArrayList<>();
for (var userRepresentation : allUsers) {
var user = convertBasicUser(userRepresentation);
for (var entry : usersByRole.entrySet()) {
if (entry.getValue().contains(user.getUserId())) {
user.getRoles().add(entry.getKey());
}
}
users.add(user);
}
users.forEach(user -> {
if (user.getRoles().contains(RED_MANAGER_ROLE)) {
user.getRoles().add(RED_USER_ROLE);
}
if (user.getRoles().contains(RED_ADMIN_ROLE)) {
user.getRoles().add(RED_USER_ADMIN_ROLE);
}
});
return users;
}
public User convertBasicUser(UserRepresentation userRepresentation) {
return User.builder()
.email(userRepresentation.getEmail())
.username(userRepresentation.getUsername())
.firstName(userRepresentation.getFirstName())
.lastName(userRepresentation.getLastName())
.userId(userRepresentation.getId())
.isActive(userRepresentation.isEnabled())
.build();
}
}

View File

@ -0,0 +1,33 @@
package com.iqser.red.keycloak.commons.model;
import java.io.Serializable;
import java.util.Set;
import java.util.TreeSet;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private String userId;
private String username;
private String email;
private String firstName;
private String lastName;
private boolean isActive;
@Builder.Default
private Set<String> roles = new TreeSet<>();
}

View File

@ -0,0 +1,179 @@
package com.iqser.red.keycloak.commons.roles;
public final class ActionRoles {
// Audit
public static final String SEARCH_AUDIT_LOG = "red-search-audit-log";
// Dictionary
public static final String ADD_DICTIONARY_ENTRY = "red-add-dictionary-entry";
public static final String ADD_DOSSIER_DICTIONARY_ENTRY = "red-add-dossier-dictionary-entry";
public static final String DELETE_DICTIONARY_ENTRY = "red-delete-dictionary-entry";
public static final String DELETE_DOSSIER_DICTIONARY_ENTRY = "red-delete-dossier-dictionary-entry";
public static final String ADD_UPDATE_DICTIONARY_TYPE = "red-add-update-dictionary-type";
public static final String ADD_UPDATE_DOSSIER_DICTIONARY_TYPE = "red-add-update-dossier-dictionary-type";
public static final String DELETE_DICTIONARY_TYPE = "red-delete-dictionary-type";
public static final String DELETE_DOSSIER_DICTIONARY_TYPE = "red-delete-dossier-dictionary-type";
public static final String READ_DICTIONARY_TYPES = "red-read-dictionary-types";
// Colors
public static final String READ_COLORS = "red-read-colors";
public static final String WRITE_COLORS = "red-write-colors";
// Digital Signature
public static final String WRITE_DIGITAL_SIGNATURE = "red-write-digital-signature";
public static final String READ_DIGITAL_SIGNATURE = "red-read-digital-signature";
// Download
public static final String PROCESS_DOWNLOAD = "red-process-download";
public static final String READ_DOWNLOAD_STATUS = "red-read-download-status";
// Legal Basis
public static final String READ_LEGAL_BASIS = "red-read-legal-basis";
public static final String WRITE_LEGAL_BASIS = "red-write-legal-basis";
// License Report
public static final String READ_LICENSE_REPORT = "red-read-license-report";
// Redaction log
public static final String READ_REDACTION_LOG = "red-read-redaction-log";
// DossierTemplates
public static final String READ_DOSSIER_TEMPLATES = "red-read-dossier-templates";
public static final String WRITE_DOSSIER_TEMPLATES = "red-write-dossier-templates";
// Dossier Status / States
public static final String READ_DOSSIER_STATUS = "red-read-dossier-status";
public static final String WRITE_DOSSIER_STATUS = "red-write-dossier-status";
// Rules
public static final String READ_RULES = "red-read-rules";
public static final String WRITE_RULES = "red-write-rules";
// SMTP
public static final String READ_SMTP_CONFIGURATION = "red-read-smtp-configuration";
public static final String WRITE_SMTP_CONFIGURATION = "red-write-smtp-configuration";
// General Configurations
public static final String READ_GENERAL_CONFIGURATION = "red-read-general-configuration";
public static final String WRITE_GENERAL_CONFIGURATION = "red-write-general-configuration";
// Preferences
public static final String MANAGE_USER_PREFERENCES = "red-manage-user-preferences";
// Users
public static final String READ_USERS = "red-read-users";
public static final String READ_ALL_USERS = "red-read-all-users";
public static final String WRITE_USERS = "red-write-users";
public static final String UPDATE_MY_PROFILE = "red-update-my-profile";
// Version
public static final String READ_VERSIONS = "red-read-versions";
// Viewed pages
public static final String MANAGE_VIEWED_PAGES = "red-manage-viewed-pages";
// Watermark
public static final String READ_WATERMARK = "red-read-watermark";
public static final String WRITE_WATERMARK = "red-write-watermark";
// Dossier
public static final String READ_DOSSIER = "red-read-dossier";
public static final String ADD_UPDATE_DOSSIER = "red-add-update-dossier";
public static final String DELETE_DOSSIER = "red-delete-dossier";
public static final String ARCHIVE_DOSSIER = "red-archived-dossier";
public static final String UNARCHIVE_DOSSIER = "red-unarchive-dossier";
//Re-analyze
public static final String REANALYZE_DOSSIER = "red-reanalyze-dossier";
public static final String REANALYZE_FILE = "red-reanalyze-file";
// Exclude Include File
public static final String EXCLUDE_INCLUDE_FILE = "red-exclude-include-file";
public static final String EXCLUDE_INCLUDE_PAGES = "red-exclude-include-pages";
// Rotate Page
public static final String ROTATE_PAGE = "red-rotate-page";
//Status
public static final String READ_FILE_STATUS = "red-read-file-status";
public static final String SET_REVIEWER = "red-set-reviewer";
public static final String SET_STATUS_UNDER_APPROVAL = "red-set-status-under-approval";
public static final String SET_STATUS_APPROVED = "red-set-status-approved";
//File Attributes
public static final String WRITE_FILE_ATTRIBUTES_CONFIG = "red-write-file-attributes-config";
public static final String READ_FILE_ATTRIBUTES_CONFIG = "red-read-file-attributes-config";
public static final String WRITE_FILE_ATTRIBUTES = "red-write-file-attributes";
//Files
public static final String UPLOAD_FILE = "red-upload-file";
public static final String DELETE_FILE = "red-delete-file";
public static final String DOWNLOAD_ORIGINAL_FILE = "red-download-original-file";
public static final String DOWNLOAD_REDACTED_FILE = "red-download-redacted-file";
public static final String DOWNLOAD_ANNOTATED_FILE = "red-download-annotated-file";
public static final String DOWNLOAD_REDACTION_PREVIEW_FILE = "red-download-redaction-preview-file";
//Manual Redaction
public static final String READ_MANUAL_REDACTIONS = "red-read-manual-redactions";
public static final String REQUEST_MANUAL_REDACTION = "red-request-redaction";
public static final String PROCESS_MANUAL_REDACTION_REQUEST = "red-process-manual-redaction-request";
public static final String DO_MANUAL_REDACTION = "red-add-redaction";
public static final String DELETE_MANUAL_REDACTION = "red-delete-manual-redaction";
public static final String ADD_COMMENT = "red-add-comment";
public static final String DELETE_COMMENT = "red-delete-comment";
//Report Template
public static final String UPLOAD_REPORT_TEMPLATE = "red-upload-report-template";
public static final String GET_REPORT_TEMPLATES = "red-get-report-templates";
public static final String DOWNLOAD_REPORT_TEMPLATE = "red-download-report-template";
public static final String DELETE_REPORT_TEMPLATE = "red-delete-report-template";
//Dossier Attributes
public static final String WRITE_DOSSIER_ATTRIBUTES_CONFIG = "red-write-dossier-attributes-config";
public static final String READ_DOSSIER_ATTRIBUTES_CONFIG = "red-read-dossier-attributes-config";
public static final String WRITE_DOSSIER_ATTRIBUTES = "red-write-dossier-attributes";
public static final String READ_DOSSIER_ATTRIBUTES = "red-read-dossier-attributes";
//Search
public static final String REINDEX = "red-reindex";
public static final String SEARCH = "red-search";
//Notifications
public static final String READ_NOTIFICATIONS = "red-read-notification";
public static final String UPDATE_NOTIFICATIONS = "red-update-notification";
// ImportedRedactions
public static final String PROCESS_TEXT_HIGHLIGHTS = "red-process-texthighlights";
public static final String DELETE_IMPORTED_REDACTIONS = "red-delete-imported-redactions";
// Highlights
public static final String GET_HIGHLIGHTS = "red-get-highlights";
public static final String CONVERT_HIGHLIGHTS = "red-convert-highlights";
public static final String DELETE_HIGHLIGHTS = "red-delete-highlights";
// ACL Permission Management Signature
public static final String MANAGE_ACL_PERMISSIONS = "red-manage-acl-permissions";
// Application Configuration
public static final String READ_APP_CONFIG = "red-read-app-configuration";
public static final String WRITE_APP_CONFIG = "red-write-app-configuration";
// License Management
public static final String UPDATE_LICENSE = "red-update-license";
public static final String READ_LICENSE = "red-read-license";
// RSS
public static final String GET_RSS = "red-get-rss";
// Multitenancy
public static final String CREATE_TENANT = "red-create-tenant";
public static final String GET_TENANTS = "red-get-tenants";
public static final String DEPLOYMENT_INFO = "red-deployment-info";
private ActionRoles() {}
}

View File

@ -0,0 +1,257 @@
package com.iqser.red.keycloak.commons.roles;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.ADD_COMMENT;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.ADD_DICTIONARY_ENTRY;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.ADD_DOSSIER_DICTIONARY_ENTRY;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.ADD_UPDATE_DICTIONARY_TYPE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.ADD_UPDATE_DOSSIER;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.ADD_UPDATE_DOSSIER_DICTIONARY_TYPE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.ARCHIVE_DOSSIER;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.CONVERT_HIGHLIGHTS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.CREATE_TENANT;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_COMMENT;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_DICTIONARY_ENTRY;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_DICTIONARY_TYPE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_DOSSIER;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_DOSSIER_DICTIONARY_ENTRY;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_DOSSIER_DICTIONARY_TYPE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_FILE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_HIGHLIGHTS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_IMPORTED_REDACTIONS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_MANUAL_REDACTION;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_REPORT_TEMPLATE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DEPLOYMENT_INFO;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DOWNLOAD_ANNOTATED_FILE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DOWNLOAD_ORIGINAL_FILE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DOWNLOAD_REDACTED_FILE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DOWNLOAD_REDACTION_PREVIEW_FILE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DOWNLOAD_REPORT_TEMPLATE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DO_MANUAL_REDACTION;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.EXCLUDE_INCLUDE_FILE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.EXCLUDE_INCLUDE_PAGES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.GET_HIGHLIGHTS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.GET_REPORT_TEMPLATES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.GET_RSS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.GET_TENANTS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.MANAGE_ACL_PERMISSIONS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.MANAGE_USER_PREFERENCES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.MANAGE_VIEWED_PAGES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.PROCESS_DOWNLOAD;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.PROCESS_MANUAL_REDACTION_REQUEST;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.PROCESS_TEXT_HIGHLIGHTS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_ALL_USERS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_APP_CONFIG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_COLORS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DICTIONARY_TYPES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DIGITAL_SIGNATURE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER_ATTRIBUTES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER_ATTRIBUTES_CONFIG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER_STATUS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER_TEMPLATES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOWNLOAD_STATUS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_FILE_ATTRIBUTES_CONFIG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_FILE_STATUS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_GENERAL_CONFIGURATION;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_LEGAL_BASIS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_LICENSE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_LICENSE_REPORT;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_MANUAL_REDACTIONS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_NOTIFICATIONS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_REDACTION_LOG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_RULES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_SMTP_CONFIGURATION;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_USERS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_VERSIONS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_WATERMARK;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.REANALYZE_DOSSIER;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.REANALYZE_FILE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.REINDEX;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.REQUEST_MANUAL_REDACTION;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.ROTATE_PAGE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.SEARCH;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.SEARCH_AUDIT_LOG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.SET_REVIEWER;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.SET_STATUS_APPROVED;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.SET_STATUS_UNDER_APPROVAL;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.UNARCHIVE_DOSSIER;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.UPDATE_LICENSE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.UPDATE_MY_PROFILE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.UPDATE_NOTIFICATIONS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.UPLOAD_FILE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.UPLOAD_REPORT_TEMPLATE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_APP_CONFIG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_COLORS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_DIGITAL_SIGNATURE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_DOSSIER_ATTRIBUTES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_DOSSIER_ATTRIBUTES_CONFIG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_DOSSIER_STATUS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_DOSSIER_TEMPLATES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_FILE_ATTRIBUTES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_FILE_ATTRIBUTES_CONFIG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_GENERAL_CONFIGURATION;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_LEGAL_BASIS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_RULES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_SMTP_CONFIGURATION;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_USERS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_WATERMARK;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Sets;
public final class ApplicationRoles {
public static final String RED_USER_ROLE = "RED_USER";
public static final String RED_MANAGER_ROLE = "RED_MANAGER";
public static final String RED_ADMIN_ROLE = "RED_ADMIN";
public static final String RED_USER_ADMIN_ROLE = "RED_USER_ADMIN";
public static final Set<String> UNMAPPED_ACTION_ROLES = Sets.newHashSet(UNARCHIVE_DOSSIER, UPDATE_LICENSE, GET_RSS);
public static final Set<String> RED_USER_ACTION_ROLES = Sets.newHashSet(ADD_COMMENT,
READ_LICENSE,
READ_APP_CONFIG,
READ_DOSSIER_STATUS,
ADD_DOSSIER_DICTIONARY_ENTRY,
DO_MANUAL_REDACTION,
ADD_UPDATE_DOSSIER_DICTIONARY_TYPE,
DELETE_COMMENT,
DELETE_DOSSIER_DICTIONARY_ENTRY,
DELETE_DOSSIER_DICTIONARY_TYPE,
DELETE_FILE,
DELETE_MANUAL_REDACTION,
DOWNLOAD_ANNOTATED_FILE,
DOWNLOAD_ORIGINAL_FILE,
DOWNLOAD_REDACTED_FILE,
DOWNLOAD_REDACTION_PREVIEW_FILE,
DOWNLOAD_REPORT_TEMPLATE,
EXCLUDE_INCLUDE_FILE,
EXCLUDE_INCLUDE_PAGES,
GET_REPORT_TEMPLATES,
MANAGE_USER_PREFERENCES,
MANAGE_VIEWED_PAGES,
PROCESS_DOWNLOAD,
PROCESS_MANUAL_REDACTION_REQUEST,
READ_COLORS,
READ_DICTIONARY_TYPES,
READ_DIGITAL_SIGNATURE,
READ_DOSSIER,
READ_DOSSIER_ATTRIBUTES,
READ_DOSSIER_ATTRIBUTES_CONFIG,
READ_DOSSIER_TEMPLATES,
READ_DOWNLOAD_STATUS,
READ_FILE_ATTRIBUTES_CONFIG,
READ_FILE_STATUS,
READ_GENERAL_CONFIGURATION,
READ_LEGAL_BASIS,
READ_MANUAL_REDACTIONS,
READ_NOTIFICATIONS,
READ_REDACTION_LOG,
READ_RULES,
READ_USERS,
READ_VERSIONS,
READ_WATERMARK,
REANALYZE_DOSSIER,
REANALYZE_FILE,
REQUEST_MANUAL_REDACTION,
ROTATE_PAGE,
SEARCH,
SEARCH_AUDIT_LOG,
SET_REVIEWER,
SET_STATUS_APPROVED,
SET_STATUS_UNDER_APPROVAL,
UPDATE_MY_PROFILE,
UPDATE_NOTIFICATIONS,
UPLOAD_FILE,
WRITE_FILE_ATTRIBUTES,
PROCESS_TEXT_HIGHLIGHTS,
GET_HIGHLIGHTS,
CONVERT_HIGHLIGHTS,
DELETE_HIGHLIGHTS,
DELETE_IMPORTED_REDACTIONS);
public static final Set<String> RED_ADMIN_ACTION_ROLES = Sets.newHashSet(ADD_DICTIONARY_ENTRY,
ADD_UPDATE_DICTIONARY_TYPE,
WRITE_DOSSIER_STATUS,
READ_DOSSIER_STATUS,
DELETE_DICTIONARY_ENTRY,
DELETE_DICTIONARY_TYPE,
DELETE_REPORT_TEMPLATE,
DOWNLOAD_REPORT_TEMPLATE,
GET_REPORT_TEMPLATES,
MANAGE_USER_PREFERENCES,
READ_COLORS,
READ_DICTIONARY_TYPES,
READ_DIGITAL_SIGNATURE,
READ_DOSSIER_ATTRIBUTES,
READ_DOSSIER_ATTRIBUTES_CONFIG,
READ_DOSSIER_TEMPLATES,
READ_FILE_ATTRIBUTES_CONFIG,
READ_LEGAL_BASIS,
READ_LICENSE_REPORT,
READ_NOTIFICATIONS,
READ_RULES,
READ_SMTP_CONFIGURATION,
READ_VERSIONS,
READ_WATERMARK,
REINDEX,
SEARCH_AUDIT_LOG,
UPDATE_NOTIFICATIONS,
UPLOAD_REPORT_TEMPLATE,
WRITE_COLORS,
WRITE_DIGITAL_SIGNATURE,
WRITE_DOSSIER_ATTRIBUTES_CONFIG,
WRITE_DOSSIER_TEMPLATES,
WRITE_FILE_ATTRIBUTES_CONFIG,
WRITE_GENERAL_CONFIGURATION,
WRITE_LEGAL_BASIS,
WRITE_RULES,
WRITE_SMTP_CONFIGURATION,
WRITE_WATERMARK,
WRITE_APP_CONFIG,
MANAGE_ACL_PERMISSIONS,
CREATE_TENANT,
GET_TENANTS,
DEPLOYMENT_INFO);
public static final Set<String> RED_MANAGER_ACTION_ROLES = Sets.newHashSet(ADD_UPDATE_DOSSIER, ARCHIVE_DOSSIER, DELETE_DOSSIER, WRITE_DOSSIER_ATTRIBUTES);
public static final Set<String> RED_USER_ADMIN_ACTION_ROLES = Sets.newHashSet(MANAGE_USER_PREFERENCES,
READ_ALL_USERS,
READ_DOSSIER,
READ_APP_CONFIG,
READ_GENERAL_CONFIGURATION,
READ_GENERAL_CONFIGURATION,
READ_NOTIFICATIONS,
READ_USERS,
UPDATE_MY_PROFILE,
UPDATE_NOTIFICATIONS,
WRITE_USERS,
READ_LICENSE);
public static final Map<String, Set<String>> ROLE_DATA = Map.of(RED_USER_ROLE,
RED_USER_ACTION_ROLES,
RED_MANAGER_ROLE,
RED_MANAGER_ACTION_ROLES,
RED_ADMIN_ROLE,
RED_ADMIN_ACTION_ROLES,
RED_USER_ADMIN_ROLE,
RED_USER_ADMIN_ACTION_ROLES);
private ApplicationRoles() {}
public static void validateRoles(Collection<String> roles) {
for (String role : roles) {
if (!ROLE_DATA.containsKey(role)) {
throw new IllegalArgumentException("Invalid Role: " + role);
}
}
}
}

View File

@ -0,0 +1,91 @@
package com.iqser.red.keycloak.commons.security;
import javax.security.cert.X509Certificate;
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCAuthenticationError;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
public class RedBearerTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
private final String issuerUrl;
public RedBearerTokenRequestAuthenticator(KeycloakDeployment deployment, String issuerUrl) {
super(deployment);
this.issuerUrl = issuerUrl;
}
// This is the exact method copied from BearerTokenRequestAuthenticator but with a custom Token Verifier.
@Override
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
this.log.debug("Verifying access_token");
if (this.log.isTraceEnabled()) {
try {
JWSInput jwsInput = new JWSInput(tokenString);
String wireString = jwsInput.getWireString();
this.log.tracef("\taccess_token: %s", wireString.substring(0, wireString.lastIndexOf(".")) + ".signature");
} catch (JWSInputException var8) {
this.log.debugf(var8, "Failed to parse access_token: %s", tokenString);
}
}
try {
this.token = RedTokenVerifier.verifyToken(tokenString, this.deployment, issuerUrl);
} catch (VerificationException var7) {
this.log.debug("Failed to verify token");
this.challenge = this.challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", var7.getMessage());
return AuthOutcome.FAILED;
}
if (this.token.getIssuedAt() < this.deployment.getNotBefore()) {
this.log.debug("Stale token");
this.challenge = this.challengeResponse(exchange, OIDCAuthenticationError.Reason.STALE_TOKEN, "invalid_token", "Stale token");
return AuthOutcome.FAILED;
} else {
boolean verifyCaller = false;
if (this.deployment.isUseResourceRoleMappings()) {
verifyCaller = this.token.isVerifyCaller(this.deployment.getResourceName());
} else {
verifyCaller = this.token.isVerifyCaller();
}
this.surrogate = null;
if (verifyCaller) {
if (this.token.getTrustedCertificates() == null || this.token.getTrustedCertificates().isEmpty()) {
this.log.warn("No trusted certificates in token");
this.challenge = this.clientCertChallenge();
return AuthOutcome.FAILED;
}
X509Certificate[] chain = new X509Certificate[0];
try {
chain = exchange.getCertificateChain();
} catch (Exception var6) {
log.debug(var6);
}
if (chain == null || chain.length == 0) {
this.log.warn("No certificates provided by undertow to verify the caller");
this.challenge = this.clientCertChallenge();
return AuthOutcome.FAILED;
}
this.surrogate = chain[0].getSubjectDN().getName();
}
this.log.debug("successful authorized");
return AuthOutcome.AUTHENTICATED;
}
}
}

View File

@ -0,0 +1,91 @@
package com.iqser.red.keycloak.commons.security;
import javax.security.cert.X509Certificate;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCAuthenticationError;
import org.keycloak.adapters.QueryParameterTokenRequestAuthenticator;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
public class RedQueryParameterTokenRequestAuthenticator extends QueryParameterTokenRequestAuthenticator {
private final String issuerUrl;
public RedQueryParameterTokenRequestAuthenticator(KeycloakDeployment deployment, String issuerUrl) {
super(deployment);
this.issuerUrl = issuerUrl;
}
// This is the exact method copied from BearerTokenRequestAuthenticator but with a custom Token Verifier.
@Override
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
this.log.debug("Verifying access_token");
if (this.log.isTraceEnabled()) {
try {
JWSInput jwsInput = new JWSInput(tokenString);
String wireString = jwsInput.getWireString();
this.log.tracef("\taccess_token: %s", wireString.substring(0, wireString.lastIndexOf(".")) + ".signature");
} catch (JWSInputException var8) {
this.log.debugf(var8, "Failed to parse access_token: %s", tokenString);
}
}
try {
this.token = RedTokenVerifier.verifyToken(tokenString, this.deployment, issuerUrl);
} catch (VerificationException var7) {
this.log.debug("Failed to verify token");
this.challenge = this.challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", var7.getMessage());
return AuthOutcome.FAILED;
}
if (this.token.getIssuedAt() < this.deployment.getNotBefore()) {
this.log.debug("Stale token");
this.challenge = this.challengeResponse(exchange, OIDCAuthenticationError.Reason.STALE_TOKEN, "invalid_token", "Stale token");
return AuthOutcome.FAILED;
} else {
boolean verifyCaller = false;
if (this.deployment.isUseResourceRoleMappings()) {
verifyCaller = this.token.isVerifyCaller(this.deployment.getResourceName());
} else {
verifyCaller = this.token.isVerifyCaller();
}
this.surrogate = null;
if (verifyCaller) {
if (this.token.getTrustedCertificates() == null || this.token.getTrustedCertificates().isEmpty()) {
this.log.warn("No trusted certificates in token");
this.challenge = this.clientCertChallenge();
return AuthOutcome.FAILED;
}
X509Certificate[] chain = new X509Certificate[0];
try {
chain = exchange.getCertificateChain();
} catch (Exception var6) {
log.debug(var6);
}
if (chain == null || chain.length == 0) {
this.log.warn("No certificates provided by undertow to verify the caller");
this.challenge = this.clientCertChallenge();
return AuthOutcome.FAILED;
}
this.surrogate = chain[0].getSubjectDN().getName();
}
this.log.debug("successful authorized");
return AuthOutcome.AUTHENTICATED;
}
}
}

View File

@ -0,0 +1,87 @@
package com.iqser.red.keycloak.commons.security;
import static org.keycloak.TokenVerifier.IS_ACTIVE;
import static org.keycloak.TokenVerifier.SUBJECT_EXISTS_CHECK;
import java.security.PublicKey;
import org.apache.commons.lang3.StringUtils;
import org.keycloak.TokenVerifier;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.rotation.PublicKeyLocator;
import org.keycloak.common.VerificationException;
import org.keycloak.representations.AccessToken;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@UtilityClass
public class RedTokenVerifier {
public AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, String issuerUrl) throws VerificationException {
TokenVerifier<AccessToken> tokenVerifier = createVerifier(tokenString, deployment, issuerUrl);
if (deployment.isVerifyTokenAudience()) {
tokenVerifier.audience(deployment.getResourceName());
}
return tokenVerifier.verify().getToken();
}
private TokenVerifier<AccessToken> createVerifier(String tokenString, KeycloakDeployment deployment, String issuerUrl) throws VerificationException {
TokenVerifier<AccessToken> tokenVerifier = TokenVerifier.create(tokenString, AccessToken.class)
.withChecks(SUBJECT_EXISTS_CHECK, IS_ACTIVE, new TokenVerifier.TokenTypeCheck("bearer"), new IssuerCheck(issuerUrl));
String kid = tokenVerifier.getHeader().getKeyId();
PublicKey publicKey = getPublicKey(kid, deployment);
tokenVerifier.publicKey(publicKey);
return tokenVerifier;
}
private PublicKey getPublicKey(String kid, KeycloakDeployment deployment) throws VerificationException {
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
if (publicKey == null) {
log.debug("Didn't find publicKey for kid: {}", kid);
throw new VerificationException("Didn't find publicKey for specified kid");
} else {
return publicKey;
}
}
@Slf4j
public static class IssuerCheck implements TokenVerifier.Predicate<AccessToken> {
private final String issuerUrl;
public IssuerCheck(String issuerUrl) {
this.issuerUrl = issuerUrl;
}
public boolean test(AccessToken t) throws VerificationException {
if (StringUtils.isEmpty(this.issuerUrl)) {
log.debug("Issuer Not Set, skipping verification");
return true;
} else if (!this.issuerUrl.equalsIgnoreCase(t.getIssuer())) {
var message = "Invalid token issuer. Expected '" + this.issuerUrl + "', but was '" + t.getIssuer() + "'";
log.debug(message);
throw new VerificationException(message);
} else {
log.debug("Issuer Verification Successful");
return true;
}
}
}
}

View File

@ -0,0 +1,156 @@
package com.iqser.red.keycloak.commons.security;
import static com.iqser.red.keycloak.commons.UserCacheBuilder.USERS_CACHE;
import java.time.Duration;
import javax.servlet.http.HttpServletRequest;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.QueryParameterTokenRequestAuthenticator;
import org.keycloak.adapters.RequestAuthenticator;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springboot.KeycloakBaseSpringBootConfiguration;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator;
import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticatorFactory;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import com.iqser.red.keycloak.commons.KeyCloakSettings;
import lombok.RequiredArgsConstructor;
@ConditionalOnProperty(value = "keycloak.enabled", havingValue = "true")
@RequiredArgsConstructor
@KeycloakConfiguration
@EnableConfigurationProperties(KeyCloakSettings.class)
@Import(KeycloakSpringBootConfigResolver.class)
public class SecuredKeyCloakConfiguration extends KeycloakWebSecurityConfigurerAdapter {
private final KeyCloakSettings keyCloakSettings;
@Bean
public KeycloakBaseSpringBootConfiguration keycloakBaseSpringBootConfiguration() {
return new KeycloakBaseSpringBootConfiguration();
}
// Submits the KeycloakAuthenticationProvider to the AuthenticationManager
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();
simpleAuthorityMapper.setPrefix("");
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(simpleAuthorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/actuator/health/**",
"/redaction-gateway-v1/async/download/with-ott/**",
"/api/async/download/with-ott/**",
"/api/docs",
"/api/docs/**",
"/",
"/api",
"/internal-api/**");
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
@Bean
@Primary
@Override
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean());
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
filter.setRequestAuthenticatorFactory(new SpringSecurityRequestAuthenticatorFactory() {
@Override
public RequestAuthenticator createRequestAuthenticator(HttpFacade facade,
HttpServletRequest request,
KeycloakDeployment deployment,
AdapterTokenStore tokenStore,
int sslRedirectPort) {
return new SpringSecurityRequestAuthenticator(facade, request, deployment, tokenStore, sslRedirectPort) {
@Override
protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
return new RedBearerTokenRequestAuthenticator(deployment, keyCloakSettings.getIssuer());
}
@Override
protected QueryParameterTokenRequestAuthenticator createQueryParameterTokenRequestAuthenticator() {
return new RedQueryParameterTokenRequestAuthenticator(deployment, keyCloakSettings.getIssuer());
}
};
}
});
return filter;
}
// Specifies the session authentication strategy
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.anonymous().disable();
http.authorizeRequests().anyRequest().authenticated();
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
}
}

View File

@ -1,4 +1,4 @@
package com.iqser.red.service.persistence.service.v1.api.model;
package com.iqser.red.keycloak.commons;
import static org.assertj.core.api.Assertions.assertThat;

View File

@ -1,8 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.annotations;
public enum AnnotationStatus {
REQUESTED,
APPROVED,
DECLINED
}

View File

@ -1,23 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate;
import java.util.ArrayList;
import java.util.List;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionarySummary;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DossierTemplateDictionaryStats {
private String dossierTemplateId;
private int numberOfDictionaries; // number of Types for the dossierTemplate
private List<DictionarySummary> dictionarySummaryList = new ArrayList<>();
}

View File

@ -1,7 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate;
public enum DossierTemplateStatus {
INCOMPLETE,
INACTIVE,
ACTIVE
}

View File

@ -1,22 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ReportTemplateUpdateRequest {
@NonNull
private String fileName;
private boolean multiFileReport;
private boolean activeByDefault;
}

View File

@ -1,7 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration;
public enum DigitalSignatureType {
CERTIFICATE,
KMS,
HSM;
}

View File

@ -1,30 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration;
import java.time.OffsetDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Watermark {
private Long id;
private String name;
private boolean enabled;
private String dossierTemplateId;
private String text;
private String hexColor;
private int opacity;
private int fontSize;
private String fontType;
private String createdBy;
private OffsetDateTime dateAdded;
private OffsetDateTime dateModified;
private WatermarkOrientation orientation;
}

View File

@ -1,7 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration;
public enum WatermarkOrientation {
VERTICAL,
HORIZONTAL,
DIAGONAL
}

View File

@ -1,8 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier;
public enum DossierAttributeType {
TEXT,
NUMBER,
DATE,
IMAGE
}

View File

@ -1,13 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier;
import lombok.Data;
@Data
public class DossierInformation {
private int numberOfActiveDossiers;
private int numberOfArchivedDossiers;
private int numberOfSoftDeletedDossiers;
private int numberOfHardDeletedDossiers;
}

View File

@ -1,37 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier;
import java.time.OffsetDateTime;
import java.util.Map;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.ProcessingStatus;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.WorkflowStatus;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = {"dossierId"})
public class DossierStats {
private String dossierId;
private int numberOfFiles;
private int numberOfSoftDeletedFiles;
private int numberOfPages; // sum of pages
private int numberOfExcludedPages; // sum of excludedPages
private boolean hasRedactionsFilePresent; // true if at least one file in the dossier has redactions
private boolean hasHintsNoRedactionsFilePresent; // true if at least one file in the dossier has hints but doesn't have redactions
private boolean hasSuggestionsFilePresent; // true if at least one file in the dossier has suggestions
private boolean hasUpdatesFilePresent; //true if at least one file in the dossier has updates
private boolean hasNoFlagsFilePresent; // true if at least one file in the dossier has none of the other flags
private Map<ProcessingStatus, Integer> fileCountPerProcessingStatus;
private Map<WorkflowStatus, Integer> fileCountPerWorkflowStatus;
private OffsetDateTime lastFileUpdateDate;
private OffsetDateTime fileManipulationDate;
}

View File

@ -1,25 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DossierStatusInfo {
@JsonProperty("dossierStatusId")
private String id;
private String name;
private String description;
private String color;
private String dossierTemplateId;
private int rank;
private Long dossierCount;
}

View File

@ -1,7 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier;
public enum DossierVisibility {
PRIVATE,
PUBLIC;
}

View File

@ -1,7 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file;
public enum FileAttributeType {
TEXT,
NUMBER,
DATE
}

View File

@ -1,8 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file;
public enum WorkflowStatus {
NEW,
UNDER_REVIEW,
UNDER_APPROVAL,
APPROVED
}

View File

@ -1,19 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DictionarySummary {
private String id; // type id
private String type; // type
private String name; // label
private long entriesCount; // entries size
}

View File

@ -1,27 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.ApplicationConfig;
@ResponseStatus(value = HttpStatus.OK)
public interface ApplicationConfigurationResource {
String APPLICATION_CONFIG_PATH = "/app-config";
@ResponseStatus(value = HttpStatus.CREATED)
@PostMapping(value = APPLICATION_CONFIG_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
ApplicationConfig createOrUpdateAppConfig(@RequestBody ApplicationConfig appConfig);
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = APPLICATION_CONFIG_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
ApplicationConfig getCurrentApplicationConfig();
}

View File

@ -1,45 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.audit.AuditModel;
import com.iqser.red.service.persistence.service.v1.api.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.model.audit.AuditSearchRequest;
import com.iqser.red.service.persistence.service.v1.api.model.audit.CategoryModel;
import com.iqser.red.service.persistence.service.v1.api.model.common.Page;
public interface AuditResource {
String PATH = "/audit";
/**
* @param auditRequest - details to audit
* @throws org.springframework.web.server.ResponseStatusException - 404 - Not Found in case the object doesn't exist
*/
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
void audit(@RequestBody AuditRequest auditRequest);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = PATH + "/search", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
Page<AuditModel> search(@RequestBody AuditSearchRequest auditSearchRequest);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = PATH + "/categories", produces = MediaType.APPLICATION_JSON_VALUE)
List<CategoryModel> getCategories();
}

View File

@ -1,43 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.component.ComponentsOverrides;
import com.iqser.red.service.persistence.service.v1.api.model.component.RevertOverrideRequest;
public interface ComponentOverrideResource {
String PATH = "/component";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String DOSSIER_ID_PARAM = "dossierId";
String DOSSIER_ID_PATH_PARAM = "/{" + DOSSIER_ID_PARAM + "}";
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void addOverrides(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ComponentsOverrides componentsOverrides);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
ComponentsOverrides getOverrides(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = PATH + "/revert" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void revertOverrides(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RevertOverrideRequest revertOverrideRequest);
}

View File

@ -1,113 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.Colors;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionaryEntry;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.Type;
@ResponseStatus(value = HttpStatus.OK)
public interface DictionaryResource {
String DICTIONARY_PATH = "/dictionary";
String TYPE_PATH = DICTIONARY_PATH + "/type";
String TYPE_PARAMETER_NAME = "type";
String TYPE_PATH_VARIABLE = "/{" + TYPE_PARAMETER_NAME + "}";
String INCLUDE_DELETED_PARAMETER_NAME = "includeDeleted";
String DOSSIER_TEMPLATE_PARAMETER_NAME = "dossierTemplateId";
String DOSSIER_TEMPLATE_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_PARAMETER_NAME + "}";
String DOSSIER_ID_PARAMETER_NAME = "dossierId";
String DOSSIER_PATH = "/dossier";
String DOSSIER_ID_PATH_VARIABLE = "/{" + DOSSIER_ID_PARAMETER_NAME + "}";
String FROM_VERSION_PARAM = "fromVersion";
String COLOR_PATH = "/color";
String VERSION_PATH = "/version";
String ENTRIES_PATH = "/entries";
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = DICTIONARY_PATH + TYPE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void addEntries(@PathVariable(TYPE_PARAMETER_NAME) String typeId,
@RequestBody List<String> entries,
@RequestParam(value = "removeCurrent", required = false, defaultValue = "false") boolean removeCurrent,
@RequestParam(value = "ignoreInvalidEntries", required = false, defaultValue = "false") boolean ignoreInvalidEntries,
@RequestParam(value = "dictionaryEntryType", required = false, defaultValue = "ENTRY") DictionaryEntryType dictionaryEntryType);
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(value = DICTIONARY_PATH + TYPE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void deleteEntries(@PathVariable(TYPE_PARAMETER_NAME) String typeId,
@RequestBody List<String> entries,
@RequestParam(value = "dictionaryEntryType", required = false, defaultValue = "ENTRY") DictionaryEntryType dictionaryEntryType);
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = TYPE_PATH + TYPE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void updateTypeValue(@PathVariable(TYPE_PARAMETER_NAME) String typeId, @RequestBody Type typeValue);
@ResponseStatus(HttpStatus.CREATED)
@PostMapping(value = TYPE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
Type addType(@RequestBody Type typeValue);
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(value = TYPE_PATH + TYPE_PATH_VARIABLE)
void deleteType(@PathVariable(TYPE_PARAMETER_NAME) String typeId);
@GetMapping(value = TYPE_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
List<Type> getAllTypesForDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = INCLUDE_DELETED_PARAMETER_NAME, required = false, defaultValue = "false") boolean includeDeleted);
@GetMapping(value = TYPE_PATH + DOSSIER_PATH + DOSSIER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
List<Type> getAllTypesForDossier(@PathVariable(DOSSIER_ID_PARAMETER_NAME) String dossierId,
@RequestParam(value = INCLUDE_DELETED_PARAMETER_NAME, required = false, defaultValue = "false") boolean includeDeleted);
@GetMapping(value = DICTIONARY_PATH + TYPE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
Type getDictionaryForType(@PathVariable(TYPE_PARAMETER_NAME) String typeId, @RequestParam(value = FROM_VERSION_PARAM, required = false) Long fromVersion);
@GetMapping(value = DICTIONARY_PATH + TYPE_PATH_VARIABLE + ENTRIES_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<DictionaryEntry> getEntriesForType(@PathVariable(TYPE_PARAMETER_NAME) String typeId,
@RequestParam(value = FROM_VERSION_PARAM, required = false) Long fromVersion,
@RequestParam(value = "dictionaryEntryType", required = false, defaultValue = "ENTRY") DictionaryEntryType dictionaryEntryType);
@GetMapping(value = VERSION_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE)
long getVersion(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
@GetMapping(value = VERSION_PATH + DOSSIER_PATH + DOSSIER_ID_PATH_VARIABLE)
long getVersionForDossier(@PathVariable(DOSSIER_ID_PARAMETER_NAME) String dossierId);
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = COLOR_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void setColors(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody Colors colors);
@ResponseBody
@GetMapping(value = COLOR_PATH + DOSSIER_TEMPLATE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
Colors getColors(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId);
}

View File

@ -1,69 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.DigitalSignature;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.DigitalSignatureKms;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.DigitalSignatureType;
@ResponseStatus(value = HttpStatus.OK)
public interface DigitalSignatureResource {
String DIGITAL_SIGNATURE_PATH = "/digital-signature";
String DIGITAL_SIGNATURE_TYPE_PATH = DIGITAL_SIGNATURE_PATH + "/type";
String DIGITAL_SIGNATURE_KMS_PATH = DIGITAL_SIGNATURE_PATH + "/kms";
String DIGITAL_SIGNATURE_TYPE = "digitalSignatureType";
String DIGITAL_SIGNATURE_TYPE_VARIABLE = "/{" + DIGITAL_SIGNATURE_TYPE + "}";
@GetMapping(value = DIGITAL_SIGNATURE_TYPE_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
DigitalSignatureType getActiveDigitalSignatureType();
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = DIGITAL_SIGNATURE_TYPE_PATH + DIGITAL_SIGNATURE_TYPE_VARIABLE)
void setActiveDigitalSignatureType(@PathVariable(DIGITAL_SIGNATURE_TYPE) DigitalSignatureType digitalSignatureType);
@ResponseStatus(HttpStatus.CREATED)
@PostMapping(value = DIGITAL_SIGNATURE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
DigitalSignature saveDigitalSignature(@RequestBody DigitalSignature digitalSignatureModel);
@ResponseStatus(HttpStatus.CREATED)
@PutMapping(value = DIGITAL_SIGNATURE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
void updateDigitalSignature(@RequestBody DigitalSignature digitalSignatureModel);
@GetMapping(value = DIGITAL_SIGNATURE_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
DigitalSignature getDigitalSignature();
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(value = DIGITAL_SIGNATURE_PATH)
void deleteDigitalSignature();
@ResponseStatus(HttpStatus.CREATED)
@PostMapping(value = DIGITAL_SIGNATURE_KMS_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
DigitalSignatureKms saveDigitalSignatureKms(@RequestBody DigitalSignatureKms digitalSignature);
@GetMapping(value = DIGITAL_SIGNATURE_KMS_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
DigitalSignatureKms getDigitalSignatureKms();
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(value = DIGITAL_SIGNATURE_KMS_PATH)
void deleteDigitalSignatureKms();
}

View File

@ -1,57 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.DossierAttributeConfig;
public interface DossierAttributesConfigResource {
String DOSSIER_ATTRIBUTE_PATH = "/dossier-attribute";
String DOSSIER_TEMPLATE_ID = "dossierTemplateId";
String DOSSIER_TEMPLATE_ID_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_ID + "}";
String DOSSIER_ATTRIBUTE_ID = "dossierAttributeId";
String DOSSIER_ATTRIBUTE_ID_PATH_VARIABLE = "/{" + DOSSIER_ATTRIBUTE_ID + "}";
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@PostMapping(value = DOSSIER_ATTRIBUTE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
DossierAttributeConfig addOrUpdateDossierAttribute(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody DossierAttributeConfig dossierAttributeConfig);
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@PutMapping(value = DOSSIER_ATTRIBUTE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<DossierAttributeConfig> setDossierAttributesConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId,
@RequestBody List<DossierAttributeConfig> dossierAttributesConfig);
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(value = DOSSIER_ATTRIBUTE_PATH + DOSSIER_ATTRIBUTE_ID_PATH_VARIABLE)
void deleteDossierAttribute(@PathVariable(DOSSIER_ATTRIBUTE_ID) String dossierAttributeId);
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = DOSSIER_ATTRIBUTE_PATH + "/delete")
void deleteDossierAttributes(@RequestBody List<String> dossierAttributeIds);
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@GetMapping(value = DOSSIER_ATTRIBUTE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
List<DossierAttributeConfig> getDossierAttributes(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId);
}

View File

@ -1,42 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.DossierAttribute;
public interface DossierAttributesResource {
String REST_PATH = "/dossierAttributes";
String SET_PATH = "/set";
String UPDATE_PATH = "/update";
String DOSSIER_ID_PARAM = "dossierId";
String DOSSIER_ID_PATH_PARAM = "/{" + DOSSIER_ID_PARAM + "}";
String DOSSIER_ATTRIBUTE_ID_PARAM = "dossierAttributeId";
String DOSSIER_ATTRIBUTE_ID_PATH_PARAM = "/{" + DOSSIER_ATTRIBUTE_ID_PARAM + "}";
@PostMapping(value = REST_PATH + SET_PATH + DOSSIER_ID_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<DossierAttribute> setDossierAttributes(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @RequestBody List<DossierAttribute> dossierAttributes);
@PostMapping(value = REST_PATH + UPDATE_PATH + DOSSIER_ID_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
DossierAttribute addOrUpdateDossierAttribute(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @RequestBody DossierAttribute dossierAttribute);
@GetMapping(value = REST_PATH + DOSSIER_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
List<DossierAttribute> getDossierAttributes(@PathVariable(DOSSIER_ID_PARAM) String dossierId);
@DeleteMapping(value = REST_PATH + SET_PATH + DOSSIER_ID_PATH_PARAM + DOSSIER_ATTRIBUTE_ID_PATH_PARAM)
void deleteDossierAttribute(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(DOSSIER_ATTRIBUTE_ID_PARAM) String dossierAttributeId);
}

View File

@ -1,115 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.CreateOrUpdateDossierRequest;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.DossierChange;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.DossierInformation;
@ResponseStatus(value = HttpStatus.OK)
public interface DossierResource {
String REST_PATH = "/dossier";
String DOSSIER_TEMPLATE_PATH = "/dossier-template";
String INFO_PATH = "/dossier-info";
String DELETED_DOSSIERS_PATH = "/deletedDossiers";
String HARD_DELETE_PATH = "/hardDelete";
String UNDELETE_PATH = "/undelete";
String ARCHIVE_DOSSIERS_PATH = "/archivedDossiers";
String ARCHIVE_PATH = "/archive";
String UNARCHIVE_PATH = "/unarchive";
String DOSSIER_ID_PARAM = "dossierId";
String DOSSIER_ID_PATH_PARAM = "/{" + DOSSIER_ID_PARAM + "}";
String DOSSIER_TEMPLATE_ID_PARAM = "dossierTemplateId";
String DOSSIER_TEMPLATE_ID_PATH_PARAM = "/{" + DOSSIER_TEMPLATE_ID_PARAM + "}";
String INCLUDE_DELETED_PARAM = "includeDeleted";
String INCLUDE_ARCHIVED_PARAM = "includeArchived";
String CHANGES_PATH = "/changes";
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = REST_PATH + CHANGES_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
Set<DossierChange> changesSince(@RequestBody JSONPrimitive<OffsetDateTime> since);
@PostMapping(value = REST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
Dossier addDossier(@RequestBody CreateOrUpdateDossierRequest dossierRequest);
@PostMapping(value = REST_PATH + DOSSIER_ID_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
Dossier updateDossier(@RequestBody CreateOrUpdateDossierRequest dossierRequest, @PathVariable(DOSSIER_ID_PARAM) String dossierId);
@DeleteMapping(value = REST_PATH + DOSSIER_ID_PATH_PARAM)
void delete(@PathVariable(DOSSIER_ID_PARAM) String dossierId);
@GetMapping(value = REST_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<Dossier> getAllDossiers(@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_DELETED_PARAM, defaultValue = "false", required = false) boolean includeDeleted);
@GetMapping(value = REST_PATH + DOSSIER_TEMPLATE_PATH + DOSSIER_TEMPLATE_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
List<Dossier> getAllDossiersForDossierTemplateId(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_DELETED_PARAM, defaultValue = "false", required = false) boolean includeDeleted);
@PostMapping(value = INFO_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
DossierInformation getDossierInformation(@RequestBody List<String> filteredDossierIds);
@GetMapping(value = REST_PATH + DOSSIER_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
Dossier getDossierById(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_DELETED_PARAM, defaultValue = "false", required = false) boolean includeDeleted);
@GetMapping(value = ARCHIVE_DOSSIERS_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<Dossier> getArchivedDossiers();
@GetMapping(value = ARCHIVE_DOSSIERS_PATH + DOSSIER_TEMPLATE_PATH + DOSSIER_TEMPLATE_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
List<Dossier> getArchivedDossiersForDossierTemplateId(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId);
@GetMapping(value = DELETED_DOSSIERS_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<Dossier> getSoftDeletedDossiers();
@DeleteMapping(value = DELETED_DOSSIERS_PATH + HARD_DELETE_PATH)
void hardDeleteDossiers(@RequestBody Set<String> dossierIds);
@PostMapping(value = DELETED_DOSSIERS_PATH + UNDELETE_PATH)
void undeleteDossiers(@RequestBody Set<String> dossierIds);
@PostMapping(value = ARCHIVE_DOSSIERS_PATH + ARCHIVE_PATH)
void archiveDossiers(@RequestBody Set<String> dossierIds);
@PostMapping(value = ARCHIVE_DOSSIERS_PATH + UNARCHIVE_PATH)
void unarchiveDossiers(@RequestBody Set<String> dossierIds);
}

View File

@ -1,31 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import java.util.Set;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.DossierStats;
public interface DossierStatsResource {
String REST_PATH = "/dossier-stats";
String DOSSIER_ID_PARAM = "dossierId";
String DOSSIER_ID_PATH_PARAM = "/{" + DOSSIER_ID_PARAM + "}";
@Deprecated
@GetMapping(value = REST_PATH + DOSSIER_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
DossierStats getDossierStats(@PathVariable(DOSSIER_ID_PARAM) String dossierId);
@Deprecated
@PostMapping(value = REST_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<DossierStats> getDossierStats(@RequestBody Set<String> dossierIds);
}

View File

@ -1,79 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.CloneDossierTemplateRequest;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.CreateOrUpdateDossierTemplateRequest;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.DossierTemplate;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.importexport.ExportDownloadRequest;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.importexport.ImportDossierTemplateRequest;
public interface DossierTemplateResource {
String DOSSIER_TEMPLATE_PATH = "/dossier-template";
String DOSSIER_TEMPLATE_ID = "dossierTemplateId";
String DOSSIER_TEMPLATE_ID_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_ID + "}";
String USER_ID_PARAM = "userId";
String CLONE_PATH = "/clone";
String EXPORT_PATH = "/export";
String IMPORT_PATH = "/import";
@ResponseBody
@ResponseStatus(HttpStatus.ACCEPTED)
@PostMapping(value = DOSSIER_TEMPLATE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
DossierTemplate createOrUpdateDossierTemplate(@RequestBody CreateOrUpdateDossierTemplateRequest dossierTemplate);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = DOSSIER_TEMPLATE_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<DossierTemplate> getAllDossierTemplates();
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = DOSSIER_TEMPLATE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
DossierTemplate getDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId);
@ResponseBody
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@DeleteMapping(value = DOSSIER_TEMPLATE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE)
void deleteDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestParam(USER_ID_PARAM) String deletingUserId);
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@PostMapping(value = DOSSIER_TEMPLATE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + CLONE_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
DossierTemplate cloneDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody CloneDossierTemplateRequest cloneDossierTemplateRequest);
@PostMapping(value = DOSSIER_TEMPLATE_PATH + EXPORT_PATH + "/prepare", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
JSONPrimitive<String> prepareExportDownload(@RequestBody ExportDownloadRequest request);
@PostMapping(value = DOSSIER_TEMPLATE_PATH + EXPORT_PATH + "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
void createExportDownload(@RequestParam String userId, @RequestParam String storageId);
@ResponseBody
@PostMapping(value = DOSSIER_TEMPLATE_PATH + IMPORT_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
DossierTemplate importDossierTemplate(@RequestBody ImportDossierTemplateRequest request);
}

View File

@ -1,35 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import java.util.Set;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.DossierTemplateDictionaryStats;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.DossierTemplateStats;
public interface DossierTemplateStatsResource {
String REST_PATH = "/dossier-template-stats";
String DICTIONARY_PATH = "/dictionary";
String DOSSIER_TEMPLATE_ID = "dossierTemplateId";
String DOSSIER_TEMPLATE_ID_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_ID + "}";
@PostMapping(value = REST_PATH + DICTIONARY_PATH, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
List<DossierTemplateDictionaryStats> getDossierTemplateDictionaryStats(@RequestBody Set<String> dossierTemplateIds);
@PostMapping(value = REST_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
DossierTemplateStats getDossierTemplateStats(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId);
@PostMapping(value = REST_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<DossierTemplateStats> getDossierTemplateStats();
}

View File

@ -1,42 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import com.iqser.red.service.persistence.service.v1.api.model.download.DownloadWithOptionRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.model.download.DownloadRequest;
import com.iqser.red.service.persistence.service.v1.api.model.download.DownloadStatus;
@ResponseStatus(value = HttpStatus.OK)
public interface DownloadResource {
String REST_PATH = "/download";
String USER_ID = "userId";
@PostMapping(value = REST_PATH + "/prepare", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
JSONPrimitive<String> prepareDownload(@RequestBody DownloadRequest request);
@PostMapping(value = REST_PATH + "/prepare-option", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
JSONPrimitive<String> prepareDownload(@RequestBody DownloadWithOptionRequest request);
@GetMapping(value = REST_PATH + "/status/{" + USER_ID + "}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<DownloadStatus> getDownloadStatus(@PathVariable(USER_ID) String userId);
@PostMapping(value = REST_PATH + "/setDownloaded", consumes = MediaType.APPLICATION_JSON_VALUE)
void setDownloaded(@RequestBody JSONPrimitive<String> setDownloadedRequest);
@PostMapping(value = REST_PATH + "/delete", consumes = MediaType.APPLICATION_JSON_VALUE)
void deleteDownloadStatus(@RequestBody JSONPrimitive<String> setDownloadedRequest);
}

View File

@ -1,77 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.FileAttributesGeneralConfiguration;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileAttributeConfig;
public interface FileAttributesConfigResource {
String FILE_ATTRIBUTES_PATH = "/fileAttributes";
String FILE_ATTRIBUTE_PATH = "/fileAttribute";
String BASE_CONFIG_PATH = "/baseConfig";
String DELETE_PATH = "/delete";
String DOSSIER_TEMPLATE_ID = "dossierTemplateId";
String DOSSIER_TEMPLATE_ID_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_ID + "}";
String FILE_ATTRIBUTE_ID = "fileAttributeId";
String FILE_ATTRIBUTE_ID_PATH_VARIABLE = "/{" + FILE_ATTRIBUTE_ID + "}";
String UTF_ENCODING = "UTF-8";
String ASCII_ENCODING = "ASCII";
String ISO_ENCODING = "ISO";
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@PutMapping(value = FILE_ATTRIBUTES_PATH + BASE_CONFIG_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
FileAttributesGeneralConfiguration setFileAttributesGeneralConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId,
@RequestBody FileAttributesGeneralConfiguration fileAttributesConfig);
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@GetMapping(value = FILE_ATTRIBUTES_PATH + BASE_CONFIG_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
FileAttributesGeneralConfiguration getFileAttributesGeneralConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId);
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@PostMapping(value = FILE_ATTRIBUTES_PATH + FILE_ATTRIBUTE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
FileAttributeConfig addOrUpdateFileAttributeConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody FileAttributeConfig fileAttributeConfig);
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@PutMapping(value = FILE_ATTRIBUTES_PATH + FILE_ATTRIBUTE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<FileAttributeConfig> setFileAttributesConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody List<FileAttributeConfig> fileAttributesConfig);
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(value = FILE_ATTRIBUTES_PATH + FILE_ATTRIBUTE_PATH + FILE_ATTRIBUTE_ID_PATH_VARIABLE)
void deleteFileAttributeConfigs(@PathVariable(FILE_ATTRIBUTE_ID) String fileAttributeId);
@ResponseStatus(HttpStatus.NO_CONTENT)
@PostMapping(value = FILE_ATTRIBUTES_PATH + FILE_ATTRIBUTE_PATH + DELETE_PATH)
void deleteFileAttributeConfigs(@RequestBody List<String> fileAttributeIds);
@ResponseBody
@ResponseStatus(HttpStatus.OK)
@GetMapping(value = FILE_ATTRIBUTES_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
List<FileAttributeConfig> getFileAttributeConfigs(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId);
}

View File

@ -1,33 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.ImportCsvRequest;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.ImportCsvResponse;
public interface FileAttributesResource {
String REST_PATH = "/fileAttributes";
String CSV_IMPORT_PATH = "/csvImport";
String SET_PATH = "/set";
String DOSSIER_ID_PARAM = "dossierId";
String DOSSIER_ID_PATH_PARAM = "/{" + DOSSIER_ID_PARAM + "}";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
@PostMapping(value = REST_PATH + CSV_IMPORT_PATH + DOSSIER_ID_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
ImportCsvResponse importCsv(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @RequestBody ImportCsvRequest importCsvRequest);
@PostMapping(value = REST_PATH + SET_PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void setFileAttributes(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody Map<String, String> fileAttributes);
}

View File

@ -1,23 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.license.LicenseReport;
import com.iqser.red.service.persistence.service.v1.api.model.license.LicenseReportRequest;
public interface LicenseReportResource {
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = "/report/license", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
LicenseReport getLicenseReport(@RequestBody LicenseReportRequest licenseReportRequest,
@RequestParam(value = "offset", defaultValue = "0") int offset,
@RequestParam(value = "limit", defaultValue = "20") int limit);
}

View File

@ -1,179 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.AddRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.Comment;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.CommentRequest;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ForceRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ImageRecategorizationRequest;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.LegalBasisChangeRequest;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualAddResponse;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.RemoveRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ResizeRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.UpdateRedactionRequest;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.IdRemoval;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualForceRedaction;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualImageRecategorization;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualLegalBasisChange;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualRedactionEntry;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualResizeRedaction;
@ResponseStatus(value = HttpStatus.OK)
public interface ManualRedactionResource {
String MANUAL_REDACTION_REST_PATH = "/manualRedaction";
String DOSSIER_ID = "dossierId";
String DOSSIER_ID_PATH_PARAM = "/{" + DOSSIER_ID + "}";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String ANNOTATION_ID = "annotationId";
String ANNOTATION_ID_PATH_VARIABLE = "/{" + ANNOTATION_ID + "}";
String COMMENT_ID = "commentId";
String COMMENT_ID_PATH_VARIABLE = "/{" + COMMENT_ID + "}";
String DELETE_PATH = "/delete";
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/add" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<ManualAddResponse> addAddRedaction(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody List<AddRedactionRequest> addRedactionRequests);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/remove" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<ManualAddResponse> addRemoveRedaction(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody List<RemoveRedactionRequest> removeRedactionRequest);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/force" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<ManualAddResponse> addForceRedaction(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody List<ForceRedactionRequest> forceRedactionRequests);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/legalBasis" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<ManualAddResponse> addLegalBasisChange(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody List<LegalBasisChangeRequest> legalBasisChangeRequests);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/recategorize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<ManualAddResponse> addImageRecategorization(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody List<ImageRecategorizationRequest> imageRecategorizationRequests);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/comment" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
Comment addComment(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@PathVariable(ANNOTATION_ID) String annotationId,
@RequestBody CommentRequest comment);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/resize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
List<ManualAddResponse> addResizeRedaction(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody List<ResizeRedactionRequest> resizeRedactionRequests);
@GetMapping(value = MANUAL_REDACTION_REST_PATH + "/add" + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
ManualRedactionEntry getAddRedaction(@PathVariable(FILE_ID) String fileId, @PathVariable(ANNOTATION_ID) String annotationId);
@GetMapping(value = MANUAL_REDACTION_REST_PATH + "/remove" + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
IdRemoval getRemoveRedaction(@PathVariable(FILE_ID) String fileId, @PathVariable(ANNOTATION_ID) String annotationId);
@GetMapping(value = MANUAL_REDACTION_REST_PATH + "/force" + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
ManualForceRedaction getForceRedaction(@PathVariable(FILE_ID) String fileId, @PathVariable(ANNOTATION_ID) String annotationId);
@GetMapping(value = MANUAL_REDACTION_REST_PATH + "/legalBasis" + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
ManualLegalBasisChange getLegalBasisChange(@PathVariable(FILE_ID) String fileId, @PathVariable(ANNOTATION_ID) String annotationId);
@GetMapping(value = MANUAL_REDACTION_REST_PATH + "/recategorize" + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
ManualImageRecategorization getImageRecategorization(@PathVariable(FILE_ID) String fileId, @PathVariable(ANNOTATION_ID) String annotationId);
@GetMapping(value = MANUAL_REDACTION_REST_PATH + "/comment" + COMMENT_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
Comment getComment(@PathVariable(COMMENT_ID) long commentId);
@GetMapping(value = MANUAL_REDACTION_REST_PATH + "/resize" + FILE_ID_PATH_VARIABLE + ANNOTATION_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
ManualResizeRedaction getResizeRedaction(@PathVariable(FILE_ID) String fileId, @PathVariable(ANNOTATION_ID) String annotationId);
@PostMapping(MANUAL_REDACTION_REST_PATH + DELETE_PATH + "/add" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void deleteAddRedaction(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody List<String> annotationIds);
@PostMapping(MANUAL_REDACTION_REST_PATH + DELETE_PATH + "/remove" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void deleteRemoveRedaction(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody List<String> annotationIds);
@PostMapping(MANUAL_REDACTION_REST_PATH + DELETE_PATH + "/force" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void deleteForceRedaction(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody List<String> annotationIds);
@PostMapping(MANUAL_REDACTION_REST_PATH + DELETE_PATH + "/legalBasis" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void deleteLegalBasisChange(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody List<String> annotationIds);
@PostMapping(MANUAL_REDACTION_REST_PATH + DELETE_PATH + "/recategorize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void deleteImageRecategorization(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody List<String> annotationIds);
@PostMapping(MANUAL_REDACTION_REST_PATH + DELETE_PATH + "/comment" + FILE_ID_PATH_VARIABLE)
void deleteComment(@PathVariable(FILE_ID) String fileId, @RequestBody List<Long> commentIds);
@PostMapping(MANUAL_REDACTION_REST_PATH + DELETE_PATH + "/resize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void deleteResizeRedaction(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody List<String> annotationIds);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/status/add" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void updateAddRedactionStatus(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody UpdateRedactionRequest updateStatusRequest);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/status/remove" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void updateRemoveRedactionStatus(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody UpdateRedactionRequest updateStatusRequest);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/status/force" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void updateForceRedactionStatus(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody UpdateRedactionRequest updateStatusRequest);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/status/legalBasis" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void updateLegalBasisChangeStatus(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody UpdateRedactionRequest updateStatusRequest);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/status/recategorize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void updateImageRecategorizationStatus(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody UpdateRedactionRequest updateStatusRequest);
@PostMapping(value = MANUAL_REDACTION_REST_PATH + "/status/resize" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void updateResizeRedactionStatus(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody UpdateRedactionRequest updateStatusRequest);
@GetMapping(value = MANUAL_REDACTION_REST_PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
ManualRedactions getManualRedactions(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId);
}

View File

@ -1,34 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.notification.NotificationPreferences;
public interface NotificationPreferencesResource {
String REST_PATH = "/notification-preferences";
String USER_ID_PARAM = "userId";
String USER_ID_PATH_PARAM = "/{" + USER_ID_PARAM + "}";
@PostMapping(value = REST_PATH + USER_ID_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE)
void setNotificationPreferences(@PathVariable(USER_ID_PARAM) String userId, @RequestBody NotificationPreferences notificationRequest);
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = REST_PATH + USER_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
NotificationPreferences getNotificationPreferences(@PathVariable(USER_ID_PARAM) String userId);
@DeleteMapping(value = REST_PATH + USER_ID_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE)
void deleteNotificationPreferences(@PathVariable(USER_ID_PARAM) String userId);
}

View File

@ -1,64 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.time.OffsetDateTime;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.audit.AddNotificationRequest;
import com.iqser.red.service.persistence.service.v1.api.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.model.notification.Notification;
@ResponseStatus(value = HttpStatus.OK)
public interface NotificationResource {
String NOTIFICATION_PATH = "/notification";
String TOGGLE_SEEN_PATH = "/toggle-seen";
String TOGGLE_READ_PATH = "/toggle-read";
String CHANGES_PATH = "/has-changes";
String USER_ID_PARAM = "userId";
String USER_ID_PATH_PARAM = "/{" + USER_ID_PARAM + "}";
String INCLUDE_SEEN_PARAM = "includeSeen";
String SET_SEEN_PARAM = "setSeen";
String SET_READ_PARAM = "setRead";
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = NOTIFICATION_PATH + CHANGES_PATH + USER_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
JSONPrimitive<Boolean> hasNewNotificationsSince(@PathVariable(USER_ID_PARAM) String userId, @RequestBody JSONPrimitive<OffsetDateTime> since);
@PostMapping(value = NOTIFICATION_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
void addNotification(@RequestBody AddNotificationRequest addNotificationRequest);
@PostMapping(value = NOTIFICATION_PATH + TOGGLE_SEEN_PATH + USER_ID_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE)
void toggleSeen(@PathVariable(USER_ID_PARAM) String userId, @RequestBody List<Long> notificationIds, @RequestParam(SET_SEEN_PARAM) boolean setSeen);
@PostMapping(value = NOTIFICATION_PATH + TOGGLE_READ_PATH + USER_ID_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE)
void toggleRead(@PathVariable(USER_ID_PARAM) String userId, @RequestBody List<Long> notificationIds, @RequestParam(SET_READ_PARAM) boolean setRead);
@DeleteMapping(value = NOTIFICATION_PATH + USER_ID_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE)
void softDelete(@PathVariable(USER_ID_PARAM) String userId, @RequestBody List<Long> notificationIds);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = NOTIFICATION_PATH + USER_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
List<Notification> getNotifications(@PathVariable(USER_ID_PARAM) String userId, @RequestParam(INCLUDE_SEEN_PARAM) boolean includeSeen);
}

View File

@ -1,74 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.pdftron.redaction.v1.api.model.ByteContentDocument;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.TextHighlightConversionRequest;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.DeleteImportedRedactionsRequest;
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public interface ReanalysisResource {
String REANALYZE_PATH = "/reanalyze";
String IMPORT_REDACTIONS_PATH = "/import-redactions";
String CONVERT_TEXT_HIGHLIGHTS_PATH = "/convert-texthighlights";
String OCR_REANALYZE_PATH = "/ocr/reanalyze";
String REINDEX_PATH = "/reindex";
String DOSSIER_ID_PARAM = "dossierId";
String DOSSIER_ID_PATH_PARAM = "/{" + DOSSIER_ID_PARAM + "}";
String FILE_ID_PARAM = "fileId";
String FILE_ID_PATH_PARAM = "/{" + FILE_ID_PARAM + "}";
String BULK_REST_PATH = "/bulk";
String FALSE = "false";
@PostMapping(value = REANALYZE_PATH + DOSSIER_ID_PATH_PARAM)
void reanalyzeDossier(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @RequestParam(value = "force", required = false, defaultValue = FALSE) boolean force);
@PostMapping(value = REANALYZE_PATH + DOSSIER_ID_PATH_PARAM + BULK_REST_PATH)
void reanalyzeFiles(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestBody Set<String> fileIds,
@RequestParam(value = "force", required = false, defaultValue = FALSE) boolean force);
@PostMapping(value = OCR_REANALYZE_PATH + DOSSIER_ID_PATH_PARAM)
void ocrDossier(@PathVariable(DOSSIER_ID_PARAM) String dossierId);
@PostMapping(value = OCR_REANALYZE_PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_PARAM)
void ocrFile(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID_PARAM) String fileId,
@RequestParam(value = "force", required = false, defaultValue = FALSE) boolean force);
@PostMapping(value = OCR_REANALYZE_PATH + DOSSIER_ID_PATH_PARAM + BULK_REST_PATH)
void ocrFiles(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @RequestBody Set<String> fileIds);
@PostMapping(value = REINDEX_PATH)
void reindex(@RequestParam(value = DOSSIER_ID_PARAM, required = false) String dossierId,
@RequestParam(value = "dropIndex", required = false, defaultValue = FALSE) boolean dropIndex,
@RequestBody Set<String> fileIds);
@PostMapping(value = IMPORT_REDACTIONS_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
void importRedactions(@RequestBody ByteContentDocument documentRequest);
@PostMapping(value = IMPORT_REDACTIONS_PATH + "/delete", consumes = MediaType.APPLICATION_JSON_VALUE)
void deleteImportedRedactions(@RequestBody DeleteImportedRedactionsRequest deleteImportedRedactionsRequest);
@PostMapping(value = CONVERT_TEXT_HIGHLIGHTS_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
void convertTextHighlights(@RequestBody TextHighlightConversionRequest textHighlightRequest);
}

View File

@ -1,48 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.redactionlog.FilteredRedactionLogRequest;
import com.iqser.red.service.redaction.v1.model.RedactionLog;
import com.iqser.red.service.redaction.v1.model.SectionGrid;
@ResponseStatus(value = HttpStatus.OK)
public interface RedactionLogResource {
String REDACTION_LOG_PATH = "/redactionLog";
String SECTION_GRID_PATH = "/sectionGrid";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String DOSSIER_ID_PARAM = "dossierId";
String DOSSIER_ID_PATH_PARAM = "/{" + DOSSIER_ID_PARAM + "}";
@GetMapping(value = REDACTION_LOG_PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
RedactionLog getRedactionLog(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "excludedType", required = false) List<String> excludedTypes,
@RequestParam(value = "withManualRedactions", required = false, defaultValue = "true") boolean withManualRedactions,
@RequestParam(value = "includeFalsePositives", required = false, defaultValue = "false") boolean includeFalsePositives);
@GetMapping(value = SECTION_GRID_PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
SectionGrid getSectionGrid(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId);
@PostMapping(value = REDACTION_LOG_PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE + "/filtered", produces = MediaType.APPLICATION_JSON_VALUE)
RedactionLog getFilteredRedactionLog(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody FilteredRedactionLogRequest filteredRedactionLogRequest);
}

View File

@ -1,57 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.ReportTemplate;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.ReportTemplateDownload;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.ReportTemplateUpdateRequest;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.ReportTemplateUploadRequest;
public interface ReportTemplateResource {
String REPORT_TEMPLATE_UPLOAD_PATH = "/templateUpload";
String DOSSIER_TEMPLATE_ID = "dossierTemplateId";
String DOSSIER_TEMPLATE_ID_PATH_VARIABLE = "/{" + DOSSIER_TEMPLATE_ID + "}";
String TEMPLATE_ID = "templateId";
String TEMPLATE_ID_PATH_VARIABLE = "/{" + TEMPLATE_ID + "}";
String DOWNLOAD_PATH = "download";
String UPDATE_PATH = "update";
@PostMapping(value = REPORT_TEMPLATE_UPLOAD_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
ReportTemplate uploadTemplate(@RequestBody ReportTemplateUploadRequest reportTemplateUploadRequest);
@GetMapping(value = REPORT_TEMPLATE_UPLOAD_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
List<ReportTemplate> getAvailableReportTemplates(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId);
@GetMapping(value = REPORT_TEMPLATE_UPLOAD_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + TEMPLATE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
ReportTemplate getReportTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @PathVariable(TEMPLATE_ID) String templateId);
@GetMapping(value = REPORT_TEMPLATE_UPLOAD_PATH + DOWNLOAD_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + TEMPLATE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
ReportTemplateDownload downloadReportTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @PathVariable(TEMPLATE_ID) String templateId);
@DeleteMapping(value = REPORT_TEMPLATE_UPLOAD_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + TEMPLATE_ID_PATH_VARIABLE)
void deleteTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @PathVariable(TEMPLATE_ID) String templateId);
@PutMapping(value = REPORT_TEMPLATE_UPLOAD_PATH + UPDATE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE + TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void updateTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId,
@PathVariable(TEMPLATE_ID) String templateId,
@RequestBody ReportTemplateUpdateRequest reportTemplateUpdateRequest);
}

View File

@ -1,46 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.SMTPConfiguration;
public interface SMTPConfigurationResource {
String SMTP_PATH = "/smtp";
String TEST_PATH = "/test";
String TEST_EMAIL = "testEmail";
String MASK_PASSWORD = "maskPassword";
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = SMTP_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
SMTPConfiguration getCurrentSMTPConfiguration(@RequestParam(value = MASK_PASSWORD, required = false, defaultValue = "true") boolean maskPassword);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@PostMapping(value = SMTP_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
void updateSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel);
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = SMTP_PATH + TEST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
void testSMTPConfiguration(@RequestParam(value = TEST_EMAIL, required = false) String testEmail, @RequestBody SMTPConfiguration smtpConfigurationModel);
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@DeleteMapping(value = SMTP_PATH)
void clearSMTPConfiguration();
}

View File

@ -1,129 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileModel;
public interface StatusResource {
String STATUS_PATH = "/status";
String DELETED_PATH = "/softdeleted";
String ALL_PATH = "/all";
String DOSSIER_ID_PARAM = "dossierId";
String DOSSIER_ID_PATH_PARAM = "/{" + DOSSIER_ID_PARAM + "}";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String CHANGES_PATH = "/has-changes";
String EXCLUDED_STATUS_PARAM = "excluded";
String EXCLUDED_FROM_AUTOMATIC_ANALYSIS_PARAM = "excludedFromAutomaticAnalysis";
String APPROVER_ID_REQUEST_PARAM = "approverId";
String ASSIGNEE_ID_REQUEST_PARAM = "assigneeId";
String USER_ID_REQUEST_PARAM = "userId";
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@PostMapping(value = STATUS_PATH + DOSSIER_ID_PATH_PARAM + CHANGES_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
JSONPrimitive<Boolean> hasChangesSince(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @RequestBody JSONPrimitive<OffsetDateTime> since);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = STATUS_PATH + DOSSIER_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
List<FileModel> getDossierStatus(@PathVariable(DOSSIER_ID_PARAM) String dossierId);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = STATUS_PATH + DOSSIER_ID_PATH_PARAM + ALL_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<FileModel> getAllDossierStatus(@PathVariable(DOSSIER_ID_PARAM) String dossierId);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = STATUS_PATH + DELETED_PATH + DOSSIER_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
List<FileModel> getSoftDeletedDossierStatus(@PathVariable(DOSSIER_ID_PARAM) String dossierId);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = STATUS_PATH + DELETED_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<FileModel> getSoftDeletedForDossierList(@RequestBody List<String> dossierIds);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = STATUS_PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
FileModel getFileStatus(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId);
@ResponseBody
@ResponseStatus(value = HttpStatus.ACCEPTED)
@PostMapping(value = STATUS_PATH + "/update-modification-date" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void updateFileModificationDate(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId);
@PostMapping(value = STATUS_PATH + "/assignee" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void setCurrentFileAssignee(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId);
@PostMapping(value = STATUS_PATH + "/underreview" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void setStatusUnderReview(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = USER_ID_REQUEST_PARAM, required = false) String userId);
@PostMapping(value = STATUS_PATH + "/underapproval" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void setStatusUnderApproval(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = APPROVER_ID_REQUEST_PARAM, required = false) String approverId);
@PostMapping(value = STATUS_PATH + "/approved" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void setStatusApproved(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = APPROVER_ID_REQUEST_PARAM, required = false) String approverId);
@PostMapping(value = STATUS_PATH + "/new" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void setStatusNew(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId);
@PostMapping(value = STATUS_PATH + "/toggle-analysis" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void toggleExclusion(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestParam(EXCLUDED_STATUS_PARAM) boolean excluded);
@PostMapping(value = STATUS_PATH + "/toggle-automatic-analysis" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void toggleAutomaticAnalysis(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(EXCLUDED_STATUS_PARAM) boolean excludedFromAutomaticAnalysis);
@PostMapping(value = STATUS_PATH + "/exclude-pages" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void excludePages(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody Set<Integer> pages);
@PostMapping(value = STATUS_PATH + "/include-pages" + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void includePages(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody Set<Integer> pages);
}

View File

@ -1,39 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.model.multitenancy.TenantRequest;
import com.iqser.red.service.persistence.service.v1.api.model.multitenancy.TenantResponse;
@ResponseStatus(value = HttpStatus.OK)
public interface TenantsResource {
String TENANT_ID_PARAM = "tenantId";
String TENANT_ID_PATH_PARAM = "/{" + TENANT_ID_PARAM + "}";
@PostMapping(value = "/tenants", consumes = MediaType.APPLICATION_JSON_VALUE)
void createTenant(@RequestBody TenantRequest tenantRequest);
@GetMapping(value = "/tenants", produces = MediaType.APPLICATION_JSON_VALUE)
List<TenantResponse> getTenants();
@GetMapping(value = "/tenants" + TENANT_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
TenantResponse getTenant(@PathVariable(TENANT_ID_PARAM) String tenantId);
@GetMapping(value = "/deploymentKey" + TENANT_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
JSONPrimitive<String> getDeploymentKey(@PathVariable(TENANT_ID_PARAM) String tenantId);
}

View File

@ -1,50 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.AddFileRequest;
@ResponseStatus(value = HttpStatus.OK)
public interface UploadResource {
String REST_PATH = "/file";
String DELETE_PATH = REST_PATH + "/delete";
String UPLOAD_PATH = REST_PATH + "/upload";
String HARD_DELETE_PATH = REST_PATH + "/hardDelete";
String UNDELETE_PATH = REST_PATH + "/undelete";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String DOSSIER_ID_PARAM = "dossierId";
String DOSSIER_ID_PATH_PARAM = "/{" + DOSSIER_ID_PARAM + "}";
@PostMapping(value = UPLOAD_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
JSONPrimitive<String> upload(@RequestBody AddFileRequest request,
@RequestParam(value = "keepManualRedactions", required = false, defaultValue = "false") boolean keepManualRedactions);
@DeleteMapping(value = DELETE_PATH + DOSSIER_ID_PATH_PARAM + FILE_ID_PATH_VARIABLE)
void deleteFile(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId);
@DeleteMapping(value = HARD_DELETE_PATH + DOSSIER_ID_PATH_PARAM)
void hardDeleteFiles(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @RequestBody Set<String> fileIds);
@PostMapping(value = UNDELETE_PATH + DOSSIER_ID_PATH_PARAM)
void undeleteFiles(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @RequestBody Set<String> fileIds);
}

View File

@ -1,42 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.fasterxml.jackson.databind.JsonNode;
public interface UserPreferenceResource {
String PREFERENCES_PATH = "/preferences";
String KEY_PARAMETER_NAME = "key";
String KEY_PATH_VARIABLE = "/{" + KEY_PARAMETER_NAME + "}";
String USER_ID_PARAMETER_NAME = "userId";
String USER_ID_PATH_VARIABLE = "/{" + USER_ID_PARAMETER_NAME + "}";
@ResponseStatus(HttpStatus.NO_CONTENT)
@PutMapping(value = PREFERENCES_PATH + USER_ID_PATH_VARIABLE + KEY_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void savePreferences(@PathVariable(USER_ID_PARAMETER_NAME) String userId, @PathVariable(KEY_PARAMETER_NAME) String key, @RequestBody JsonNode jsonNode);
@ResponseBody
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = PREFERENCES_PATH + USER_ID_PATH_VARIABLE + KEY_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
JsonNode getPreferences(@PathVariable(USER_ID_PARAMETER_NAME) String userId, @PathVariable(KEY_PARAMETER_NAME) String key);
@ResponseBody
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@DeleteMapping(value = PREFERENCES_PATH + USER_ID_PATH_VARIABLE + KEY_PATH_VARIABLE)
void deletePreferences(@PathVariable(USER_ID_PARAMETER_NAME) String userId, @PathVariable(KEY_PARAMETER_NAME) String key);
}

View File

@ -1,39 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ViewedPage;
@ResponseStatus(value = HttpStatus.OK)
public interface ViewedPagesResource {
String REST_PATH = "/viewedPages";
String FILE_ID = "fileId";
String FILE_ID_PATH_VARIABLE = "/{" + FILE_ID + "}";
String ROLE = "role";
String ROLE_PATH_VARIABLE = "/{" + ROLE + "}";
@PostMapping(value = REST_PATH + FILE_ID_PATH_VARIABLE + ROLE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void addPage(@PathVariable(FILE_ID) String fileId, @PathVariable(ROLE) String role, @RequestBody Integer page);
@DeleteMapping(value = REST_PATH + FILE_ID_PATH_VARIABLE + ROLE_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
void removePage(@PathVariable(FILE_ID) String fileId, @PathVariable(ROLE) String role, @RequestBody Integer page);
@GetMapping(value = REST_PATH + FILE_ID_PATH_VARIABLE + ROLE_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
List<ViewedPage> getViewedPages(@PathVariable(FILE_ID) String fileId, @PathVariable(ROLE) String role);
}

View File

@ -1,51 +0,0 @@
package com.iqser.red.service.persistence.service.v1.api.resources;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.iqser.red.service.persistence.service.v1.api.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.Watermark;
@ResponseStatus(value = HttpStatus.OK)
public interface WatermarkResource {
String WATERMARK_PATH = "/watermark";
String CHECK_USED_REST_PATH = "/used";
String WATERMARK_ID_PARAMETER_NAME = "watermarkId";
String WATERMARK_ID_PATH_VARIABLE = "/{" + WATERMARK_ID_PARAMETER_NAME + "}";
String DOSSIER_TEMPLATE_ID_PARAMETER_NAME = "dossierTemplateId";
@ResponseStatus(HttpStatus.OK)
@PostMapping(value = WATERMARK_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
Watermark createOrUpdateWatermark(@RequestBody Watermark watermark);
@GetMapping(value = WATERMARK_PATH + WATERMARK_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
Watermark getWatermark(@PathVariable(WATERMARK_ID_PARAMETER_NAME) long watermarkId);
@GetMapping(value = WATERMARK_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
List<Watermark> getWatermarksForDossierTemplateId(@RequestParam(DOSSIER_TEMPLATE_ID_PARAMETER_NAME) String dossierTemplateId);
@ResponseStatus(HttpStatus.NO_CONTENT)
@DeleteMapping(value = WATERMARK_PATH + WATERMARK_ID_PATH_VARIABLE)
void deleteWatermark(@PathVariable(WATERMARK_ID_PARAMETER_NAME) long watermarkId);
@ResponseStatus(value = HttpStatus.OK)
@GetMapping(value = WATERMARK_PATH + CHECK_USED_REST_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
JSONPrimitive<Boolean> isWatermarkUsed(@RequestParam(WATERMARK_ID_PARAMETER_NAME) long watermarkId);
}

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>persistence-service-v1</artifactId>
<groupId>com.iqser.red.service</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>persistence-service-external-api-impl-v1</artifactId>
<properties>
<slf4j.version>1.7.30</slf4j.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>persistence-service-processor-v1</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,11 @@
package com.iqser.red.persistence.service.v1.external.api.impl;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class PersistenceServiceExternalApiConfiguration {
}

View File

@ -0,0 +1,42 @@
package com.iqser.red.persistence.service.v1.external.api.impl.cache;
import static com.iqser.red.persistence.service.v1.external.api.impl.service.OneTimeTokenCacheService.OTT_CACHE;
import java.time.Duration;
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
@Configuration
public class PersistenceServiceExternalApiCacheConfiguration {
public static final String RATE_LIMITER_CACHE = "buckets";
public static final String ACL_CACHE = "acl";
@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(1))
.disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder.withCacheConfiguration(RATE_LIMITER_CACHE, RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(60)))
.withCacheConfiguration(ACL_CACHE, RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(1)).serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()))
)
.withCacheConfiguration(OTT_CACHE, RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(10)));
}
}

View File

@ -0,0 +1,45 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_APP_CONFIG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_APP_CONFIG;
import javax.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.ApplicationConfigurationEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.ApplicationConfigService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.ApplicationConfigurationResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.ApplicationConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class ApplicationConfigurationController implements ApplicationConfigurationResource {
private final ApplicationConfigService applicationConfigService;
@Override
@PreAuthorize("hasAuthority('" + WRITE_APP_CONFIG + "')")
public ApplicationConfig createOrUpdateAppConfig(@Valid @RequestBody ApplicationConfig appConfig) {
return MagicConverter.convert(applicationConfigService.saveApplicationConfiguration(MagicConverter.convert(appConfig, ApplicationConfigurationEntity.class)),
ApplicationConfig.class);
}
@Override
@PreAuthorize("hasAuthority('" + READ_APP_CONFIG + "')")
public ApplicationConfig getCurrentApplicationConfig() {
return MagicConverter.convert(applicationConfigService.getApplicationConfig(), ApplicationConfig.class);
}
}

View File

@ -0,0 +1,56 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.SEARCH_AUDIT_LOG;
import static com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter.convert;
import java.util.List;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.AuditResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditSearchRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.CategoryModel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class AuditController implements AuditResource {
private final AuditPersistenceService auditPersistenceService;
@Override
@PreAuthorize("hasAuthority('" + SEARCH_AUDIT_LOG + "')")
public AuditResponse searchAuditLog(@RequestBody AuditSearchRequest auditSearchRequest) {
var auditModels = convert(auditPersistenceService.search(auditSearchRequest), AuditModel.class);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(auditSearchRequest.getObjectId())
.category(AuditCategory.AUDIT.name())
.message("Audit Log has been viewed.")
.build());
return new AuditResponse(auditModels.getElements(), auditModels.getTotalHits(), auditModels.getPage(), auditModels.getPageSize());
}
@Override
@PreAuthorize("hasAuthority('" + SEARCH_AUDIT_LOG + "')")
public List<CategoryModel> getAuditCategories() {
return auditPersistenceService.getCategories();
}
}

View File

@ -0,0 +1,65 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.MANAGE_ACL_PERMISSIONS;
import java.util.List;
import java.util.Set;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.service.CustomPermissionService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.CustomPermissionMappingResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.permission.CustomPermissionMappingModel;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class CustomPermissionMappingController implements CustomPermissionMappingResource {
private final CustomPermissionService customPermissionService;
@Override
@PreAuthorize("hasAuthority('" + MANAGE_ACL_PERMISSIONS + "')")
public List<CustomPermissionMappingModel> getCustomPermissionMappings(@PathVariable(TARGET_OBJECT_NAME) String targetObject) {
return customPermissionService.getCustomPermissionMappings(targetObject);
}
@Override
@PreAuthorize("hasAuthority('" + MANAGE_ACL_PERMISSIONS + "')")
public void saveCustomPermissionMappings(@PathVariable(TARGET_OBJECT_NAME) String targetObject, @RequestBody List<CustomPermissionMappingModel> customPermissionMappingModels) {
customPermissionService.saveCustomPermissionMappings(targetObject, customPermissionMappingModels);
}
@Override
@PreAuthorize("hasAuthority('" + MANAGE_ACL_PERMISSIONS + "')")
public List<CustomPermissionMappingModel> getValidMapping(@PathVariable(TARGET_OBJECT_NAME) String targetObject) {
return customPermissionService.getExistingPermissions(targetObject);
}
@Override
@PreAuthorize("hasAuthority('" + MANAGE_ACL_PERMISSIONS + "')")
public Set<String> getAllSupportedTargetObjects() {
return customPermissionService.getAllSupportedTargetObjects();
}
@Override
@PreAuthorize("hasAuthority('" + MANAGE_ACL_PERMISSIONS + "')")
public void syncAllCustomPermissions() {
customPermissionService.syncAllCustomPermissions();
}
}

View File

@ -0,0 +1,330 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.management.v1.processor.utils.TypeValueMapper;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DictionaryResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.CreateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.Dictionary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.UpdateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.Colors;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;
import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class DictionaryController implements DictionaryResource {
private final DictionaryService dictionaryService;
private final AuditPersistenceService auditClient;
@Override
public void addEntry(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestBody List<String> entries,
@RequestParam(name = REMOVE_CURRENT_REQUEST_PARAM, defaultValue = "false", required = false) boolean removeCurrent,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
@RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType) {
addEntries(type, dossierTemplateId, entries, removeCurrent, dossierId, dictionaryEntryType);
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary entries were added.")
.details(Map.of("Type", type, "Number", entries.size()))
.build());
}
private void addEntries(String type, String dossierTemplateId, List<String> entries, boolean removeCurrent, String dossierId, DictionaryEntryType dictionaryEntryType) {
if (dossierId == null) {
dictionaryService.addGlobalEntries(type, dossierTemplateId, entries, removeCurrent, dictionaryEntryType);
} else {
dictionaryService.addDossierEntries(type, dossierTemplateId, entries, removeCurrent, dossierId, dictionaryEntryType);
}
}
@Override
public void deleteEntry(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@PathVariable(ENTRY_PARAMETER_NAME) String entry,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
@RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType) {
deleteEntries(type, dossierTemplateId, Arrays.asList(entry), dossierId, dictionaryEntryType);
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary entry was deleted.")
.details(Map.of("Type", type, "Value", entry))
.build());
}
@Override
public void deleteEntries(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestBody List<String> entries,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
@RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType) {
if (dossierId == null) {
dictionaryService.deleteGlobalEntries(type, dossierTemplateId, entries, dictionaryEntryType);
} else {
dictionaryService.deleteDossierEntries(type, dossierTemplateId, entries, dossierId, dictionaryEntryType);
}
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary entries were deleted.")
.details(Map.of("Type", type, "Number", entries.size()))
.build());
}
@Override
public void updateType(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestBody UpdateTypeValue typeValue,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId) {
if (dossierId == null) {
dictionaryService.updateGlobalType(type, dossierTemplateId, typeValue);
} else {
dictionaryService.updateDossierType(type, dossierTemplateId, typeValue, dossierId);
}
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was updated.")
.details(Map.of("Type", type))
.build());
}
@Override
public TypeValue addType(@Valid @RequestBody CreateTypeValue typeValue, @RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId) {
Type result;
if (dossierId == null) {
result = dictionaryService.addGlobalType(typeValue);
} else {
result = dictionaryService.addDossierType(typeValue, dossierId);
}
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(typeValue.getDossierTemplateId())
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was added.")
.details(Map.of("Type", typeValue.getType()))
.build());
var converted = MagicConverter.convert(result, TypeValue.class, new TypeValueMapper());
return converted;
}
@Override
public void deleteType(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId) {
if (dossierId == null) {
dictionaryService.deleteGlobalType(type, dossierTemplateId);
} else {
dictionaryService.deleteDossierType(type, dossierTemplateId, dossierId);
}
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was deleted.")
.details(Map.of("Type", type))
.build());
}
@Override
public void deleteTypes(@RequestBody List<String> types,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId) {
List<String> errorIds = new ArrayList<>();
for (var type : types) {
try {
if (dossierId == null) {
dictionaryService.deleteGlobalType(type, dossierTemplateId);
} else {
dictionaryService.deleteDossierType(type, dossierTemplateId, dossierId);
}
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary type was deleted.")
.details(Map.of("Type", type))
.build());
} catch (FeignException e) {
errorIds.add(type);
}
}
if (errorIds.size() > 0) {
throw new BadRequestException("Failed to delete dictionary types: " + errorIds);
}
}
@Override
public TypeResponse getAllTypes(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
@RequestParam(value = INCLUDE_DELETED_PARAMETER_NAME, required = false, defaultValue = "false") boolean includeDeleted) {
return dictionaryService.getAllTypes(dossierTemplateId, dossierId, includeDeleted);
}
@Override
public Colors getColors(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
return dictionaryService.getColors(dossierTemplateId);
}
@Override
public void uploadDictionary(@RequestPart(name = "file", required = false) MultipartFile file,
@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
@RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType) {
validateFile(file);
try {
addEntries(type, dossierTemplateId, new String(file.getBytes(), StandardCharsets.UTF_8).lines().collect(Collectors.toList()), true, dossierId, dictionaryEntryType);
} catch (IOException e) {
log.debug(e.getMessage(), e);
throw new BadRequestException("Could not upload file.", e);
}
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DICTIONARY.name())
.message("Dictionary has been uploaded.")
.details(Map.of("Type", type))
.build());
}
private void validateFile(@RequestPart(name = "file", required = false) MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new BadRequestException("File cannot be null or empty");
}
}
@Override
public ResponseEntity<?> downloadDictionary(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
@RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType) {
byte[] data = null;
switch (dictionaryEntryType) {
case ENTRY:
data = String.join("\n", getDictionaryForType(type, dossierTemplateId, dossierId).getEntries()).getBytes();
break;
case FALSE_POSITIVE:
data = String.join("\n", getDictionaryForType(type, dossierTemplateId, dossierId).getFalsePositiveEntries()).getBytes();
break;
case FALSE_RECOMMENDATION:
data = String.join("\n", getDictionaryForType(type, dossierTemplateId, dossierId).getFalseRecommendationEntries()).getBytes();
break;
}
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
httpHeaders.add("Content-Disposition", "attachment; filename*=utf-8''" + StringEncodingUtils.urlEncode(type) + ".txt");
InputStream is = new ByteArrayInputStream(data);
return new ResponseEntity<>(new InputStreamResource(is), httpHeaders, HttpStatus.OK);
}
@Override
public Dictionary getDictionaryForType(@PathVariable(TYPE_PARAMETER_NAME) String type,
@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
@RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId) {
return dictionaryService.getDictionaryForType(type, dossierTemplateId, dossierId);
}
@Override
public void setColors(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody Colors colors) {
dictionaryService.setColors(dossierTemplateId, colors);
auditClient.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Colors have been changed.")
.build());
}
}

View File

@ -0,0 +1,213 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DIGITAL_SIGNATURE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_DIGITAL_SIGNATURE;
import java.nio.charset.StandardCharsets;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DigitalSignatureEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.DigitalSignatureKmsEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.DigitalSignatureKmsService;
import com.iqser.red.service.persistence.management.v1.processor.service.DigitalSignatureService;
import com.iqser.red.service.persistence.management.v1.processor.service.DigitalSignatureTypeService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DigitalSignatureResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DigitalSignatureKms;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DigitalSignatureKmsViewModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DigitalSignatureViewModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.DigitalSignature;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.DigitalSignatureType;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class DigitalSignatureController implements DigitalSignatureResource {
private static final String DIGITAL_SIGNATURE_AUDIT_ID = "DigitalSignature";
private final DigitalSignatureTypeService digitalSignatureTypeService;
private final DigitalSignatureService digitalSignatureService;
private final DigitalSignatureKmsService digitalSignatureKmsService;
private final AuditPersistenceService auditPersistenceService;
@Override
@PreAuthorize("hasAuthority('" + READ_DIGITAL_SIGNATURE + "')")
public DigitalSignatureType getActiveDigitalSignatureType() {
return digitalSignatureTypeService.getActiveDigitalSignatureType();
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DIGITAL_SIGNATURE + "')")
public void setActiveDigitalSignatureType(DigitalSignatureType digitalSignatureType) {
digitalSignatureTypeService.setActiveDigitalSignatureType(digitalSignatureType);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature type has been updated.")
.build());
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DIGITAL_SIGNATURE + "')")
public DigitalSignatureViewModel saveDigitalSignature(@RequestBody DigitalSignature digitalSignatureModel) {
DigitalSignatureViewModel digitalSignatureViewModel = convertToView(digitalSignatureService.saveDigitalSignature(convert(digitalSignatureModel)));
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature has been saved.")
.build());
return digitalSignatureViewModel;
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DIGITAL_SIGNATURE + "')")
public void updateDigitalSignature(@RequestBody DigitalSignatureViewModel digitalSignatureModel) {
digitalSignatureService.updateDigitalSignature(convert(digitalSignatureModel));
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature has been updated.")
.build());
}
@Override
@PreAuthorize("hasAuthority('" + READ_DIGITAL_SIGNATURE + "')")
public DigitalSignatureViewModel getDigitalSignature() {
return convertToView(digitalSignatureService.getDigitalSignature());
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DIGITAL_SIGNATURE + "')")
public void deleteDigitalSignature() {
digitalSignatureService.deleteDigitalSignature();
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital signature has been deleted.")
.build());
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DIGITAL_SIGNATURE + "')")
public DigitalSignatureKmsViewModel saveDigitalSignatureKms(@RequestBody DigitalSignatureKms digitalSignature) {
DigitalSignatureKmsViewModel result = convert(digitalSignatureKmsService.saveDigitalSignature(convert(digitalSignature)));
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital KMS signature has been saved.")
.build());
return result;
}
@Override
@PreAuthorize("hasAuthority('" + READ_DIGITAL_SIGNATURE + "')")
public DigitalSignatureKmsViewModel getDigitalSignatureKms() {
return convert(digitalSignatureKmsService.getDigitalSignature());
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DIGITAL_SIGNATURE + "')")
public void deleteDigitalSignatureKms() {
digitalSignatureKmsService.deleteDigitalSignature();
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(DIGITAL_SIGNATURE_AUDIT_ID)
.category(AuditCategory.SETTINGS.name())
.message("Digital KMS signature has been deleted.")
.build());
}
private DigitalSignatureKmsViewModel convert(DigitalSignatureKmsEntity digitalSignature) {
return DigitalSignatureKmsViewModel.builder()
.kmsAccessKey(digitalSignature.getKmsAccessKey())
.kmsKeyId(digitalSignature.getKmsKeyId())
.kmsRegion(digitalSignature.getKmsRegion())
.kmsServiceEndpoint(digitalSignature.getKmsServiceEndpoint())
.certificateName(digitalSignature.getCertificateName())
.build();
}
private DigitalSignatureKmsEntity convert(DigitalSignatureKms digitalSignatureKms) {
return DigitalSignatureKmsEntity.builder()
.certificate(digitalSignatureKms.getCertificate().getBytes(StandardCharsets.UTF_8))
.kmsAccessKey(digitalSignatureKms.getKmsAccessKey())
.kmsKeyId(digitalSignatureKms.getKmsKeyId())
.kmsRegion(digitalSignatureKms.getKmsRegion())
.kmsSecretKey(digitalSignatureKms.getKmsSecretKey())
.kmsServiceEndpoint(digitalSignatureKms.getKmsServiceEndpoint())
.certificateName(digitalSignatureKms.getCertificateName())
.build();
}
private DigitalSignatureEntity convert(DigitalSignatureViewModel digitalSignature) {
return DigitalSignatureEntity.builder()
.certificateName(digitalSignature.getCertificateName())
.contactInfo(digitalSignature.getContactInfo())
.location(digitalSignature.getLocation())
.reason(digitalSignature.getReason())
.build();
}
public DigitalSignatureViewModel convertToView(DigitalSignatureEntity model) {
return DigitalSignatureViewModel.builder()
.certificateName(model.getCertificateName())
.contactInfo(model.getContactInfo())
.location(model.getLocation())
.reason(model.getReason())
.build();
}
public DigitalSignatureEntity convert(DigitalSignature digitalSignature) {
return DigitalSignatureEntity.builder()
.certificateName(digitalSignature.getCertificateName())
.contactInfo(digitalSignature.getContactInfo())
.location(digitalSignature.getLocation())
.password(digitalSignature.getPassword())
.reason(digitalSignature.getReason())
.privateKey(Base64Utils.decodeFromString(digitalSignature.getPrivateKey()))
.build();
}
}

View File

@ -0,0 +1,178 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER_ATTRIBUTES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER_ATTRIBUTES_CONFIG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_DOSSIER_ATTRIBUTES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_DOSSIER_ATTRIBUTES_CONFIG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_FILE_ATTRIBUTES;
import java.util.List;
import java.util.Map;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierAttributeConfigEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierAttributesManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DossierAttributesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierAttributes;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierAttributesConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierAttributeConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierAttribute;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class DossierAttributesController implements DossierAttributesResource {
private final DossierAttributeConfigPersistenceService dossierAttributeConfigPersistenceService;
private final AuditPersistenceService auditPersistenceService;
private final DossierAttributesManagementService dossierAttributesManagementService;
private final AccessControlService accessControlService;
@Override
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_ATTRIBUTES_CONFIG + "')")
public DossierAttributesConfig setDossierAttributesConfig(String dossierTemplateId, DossierAttributesConfig dossierAttributesConfig) {
var result = MagicConverter.convert(dossierAttributeConfigPersistenceService.setDossierAttributesConfig(dossierTemplateId,
MagicConverter.convert(dossierAttributesConfig.getDossierAttributeConfigs(), DossierAttributeConfigEntity.class)), DossierAttributeConfig.class);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Changed dossier attributes base configuration.")
.build());
return new DossierAttributesConfig(result);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_ATTRIBUTES_CONFIG + "')")
public DossierAttributeConfig addOrUpdateDossierAttributeConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId,
@RequestBody DossierAttributeConfig dossierAttribute) {
var result = MagicConverter.convert(dossierAttributeConfigPersistenceService.addOrUpdateDossierAttribute(dossierTemplateId,
MagicConverter.convert(dossierAttribute, DossierAttributeConfigEntity.class)), DossierAttributeConfig.class);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier attributes added/updated")
.build());
return result;
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_ATTRIBUTES_CONFIG + "')")
public void deleteDossierAttributeConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @PathVariable(DOSSIER_ATTRIBUTE_ID) String dossierAttributeId) {
dossierAttributeConfigPersistenceService.deleteDossierAttribute(dossierAttributeId);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier attributes removed")
.details(Map.of("DossierAttributeId", dossierAttributeId))
.build());
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_ATTRIBUTES_CONFIG + "')")
public void deleteDossierAttributesConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestParam(DOSSIER_ATTRIBUTE_IDS) List<String> dossierAttributeIds) {
dossierAttributeConfigPersistenceService.deleteDossierAttributes(dossierAttributeIds);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier attributes removed")
.details(Map.of("DossierAttributeId", dossierAttributeIds))
.build());
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER_ATTRIBUTES_CONFIG + "')")
public DossierAttributesConfig getDossierAttributesConfig(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {
var result = dossierAttributeConfigPersistenceService.getDossierAttributes(dossierTemplateId);
return new DossierAttributesConfig(MagicConverter.convert(result, DossierAttributeConfig.class));
}
@PreAuthorize("hasAuthority('" + WRITE_FILE_ATTRIBUTES + "')")
public DossierAttributes setDossierAttributes(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody DossierAttributes dossierAttributes) {
accessControlService.verifyUserIsDossierOwner(dossierId);
var result = dossierAttributesManagementService.setDossierAttributes(dossierId, dossierAttributes.getDossierAttributeList());
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Changed dossier attributes.")
.build());
return new DossierAttributes(result);
}
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_ATTRIBUTES + "')")
public DossierAttributes addOrUpdateDossierAttribute(String dossierId, DossierAttribute dossierAttribute) {
accessControlService.verifyUserIsDossierOwner(dossierId);
DossierAttribute result = dossierAttributesManagementService.addOrUpdateDossierAttribute(dossierId, dossierAttribute);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Added or updated dossier attributes.")
.build());
return new DossierAttributes(List.of(result)); // TODO should be single Object???
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER_ATTRIBUTES + "')")
public DossierAttributes getDossierAttributes(String dossierId) {
var result = dossierAttributesManagementService.getDossierAttributes(dossierId);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Got dossier attributes.")
.build());
return new DossierAttributes(result);
}
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_ATTRIBUTES + "')")
public void deleteDossierAttribute(String dossierId, String dossierAttributeId) {
accessControlService.verifyUserIsDossierOwner(dossierId);
dossierAttributesManagementService.deleteDossierAttribute(dossierId, dossierAttributeId);
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Changed dossier attributes.")
.build());
}
}

View File

@ -0,0 +1,507 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.ADD_UPDATE_DOSSIER;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.ARCHIVE_DOSSIER;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_DOSSIER;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.UNARCHIVE_DOSSIER;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Lists;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.keycloak.commons.model.User;
import com.iqser.red.keycloak.commons.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.UserService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DossierResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierChangeEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierInformation;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AddNotificationRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.notification.NotificationType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class DossierController implements DossierResource {
private static final Set<String> VALID_MEMBER_ROLES = Set.of(ApplicationRoles.RED_USER_ROLE, ApplicationRoles.RED_MANAGER_ROLE);
private final DossierManagementService dossierManagementService;
private final UserService userService;
private final FileStatusManagementService fileStatusManagementService;
private final AuditPersistenceService auditPersistenceService;
private final NotificationPersistenceService notificationPersistenceService;
private final AccessControlService accessControlService;
private final DossierACLService dossierACLService;
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
public DossierInformation getDossierInformation() {
return dossierManagementService.getDossierInformation(dossierACLService.getDossierIdsWithViewPermission());
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.dossierId, 'Dossier', 'VIEW_OBJECT')")
public List<DossierChangeEntry> changesSince(@RequestBody JSONPrimitive<OffsetDateTime> since) {
return dossierManagementService.changesSince(since)
.stream()
.map(d -> new DossierChangeEntry(d.getDossierId(), d.isDossierChanges(), d.isFileChanges()))
.collect(Collectors.toList());
}
@Override
@PreAuthorize("hasAuthority('" + ADD_UPDATE_DOSSIER + "') && (#dossierRequest.dossierId == null || hasPermission(#dossierRequest.dossierId, 'Dossier', 'ACCESS_OBJECT') )")
public ResponseEntity<Dossier> createDossierOrUpdateDossier(@RequestBody DossierRequest dossierRequest) {
if (dossierRequest.getDossierId() != null && dossierRequest.getOwnerId() == null) {
throw new BadRequestException("Owner must be set for any update");
}
// this code always executes - members, approvers and owner must be valid
String ownerId = getAndValidateOwnerId(dossierRequest.getOwnerId());
Set<String> members = getAndValidateMembers(ownerId, dossierRequest.getMemberIds());
Set<String> approvers = getAndValidateMembers(ownerId, dossierRequest.getApproverIds());
members.addAll(approvers);
if ((dossierRequest.getDownloadFileTypes() == null || dossierRequest.getDownloadFileTypes()
.isEmpty()) && (dossierRequest.getReportTemplateIds() == null || dossierRequest.getReportTemplateIds().isEmpty())) {
throw new BadRequestException("Download and report types cannot both be empty");
}
// if dossierId is set - load dossier
if (StringUtils.isNotEmpty(dossierRequest.getDossierId())) {
Dossier existingDossier = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierRequest.getDossierId(), true, false));
if (existingDossier.getArchivedTime() != null) {
checkValidityForArchivedDossierUpdateRequest(dossierRequest, existingDossier);
}
// update using data from request and computed owner/members/approvers
Dossier updatedDossier = dossierManagementService.updateDossier(CreateOrUpdateDossierRequest.builder()
.dossierName(dossierRequest.getDossierName())
.description(dossierRequest.getDescription())
.dossierTemplateId(dossierRequest.getDossierTemplateId())
.downloadFileTypes(dossierRequest.getDownloadFileTypes())
.dueDate(dossierRequest.getDueDate())
.reportTemplateIds(new ArrayList<>(dossierRequest.getReportTemplateIds()))
.watermarkId(dossierRequest.getWatermarkId())
.previewWatermarkId(dossierRequest.getPreviewWatermarkId())
.dossierStatusId(dossierRequest.getDossierStatusId())
.build(), existingDossier.getId());
dossierACLService.updateDossierACL(members, approvers, ownerId, updatedDossier.getId());
dossierACLService.enhanceDossierWithACLData(updatedDossier);
updateFileStatusForDossierFiles(updatedDossier.getId(), members);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(updatedDossier.getId())
.category(AuditCategory.DOSSIER.name())
.message("Dossier has been updated.")
.build());
if (existingDossier.getOwnerId() == null || !existingDossier.getOwnerId().equals(ownerId)) {
if (ownerId != null && !ownerId.equals(KeycloakSecurity.getUserId())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(ownerId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOSSIER_OWNER_SET.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build());
}
if (existingDossier.getOwnerId() != null && !existingDossier.getOwnerId().equals(KeycloakSecurity.getUserId())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(existingDossier.getOwnerId())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOSSIER_OWNER_REMOVED.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build());
}
}
Stream.concat(members.stream(), approvers.stream())
.filter(member -> !member.equals(ownerId) && !member.equals(KeycloakSecurity.getUserId()) && (existingDossier.getMemberIds() == null || !existingDossier.getMemberIds()
.contains(member)))
.forEach(member -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_BECOMES_DOSSIER_MEMBER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
if (existingDossier.getMemberIds() != null) {
existingDossier.getMemberIds()
.stream()
.filter(member -> !members.contains(member) && !approvers.contains(member) && !member.equals(KeycloakSecurity.getUserId()))
.forEach(member -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_REMOVED_AS_DOSSIER_MEMBER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
}
approvers.stream()
.filter(approver -> !KeycloakSecurity.getUserId().equals(approver) && existingDossier.getMemberIds() != null && existingDossier.getMemberIds()
.contains(approver) && (existingDossier.getApproverIds() == null || !existingDossier.getApproverIds().contains(approver)))
.forEach(approver -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(approver)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_PROMOTED_TO_APPROVER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
members.stream()
.filter(member -> !member.equals(KeycloakSecurity.getUserId()) && existingDossier.getApproverIds() != null && existingDossier.getApproverIds()
.contains(member) && !approvers.contains(member))
.forEach(member -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.USER_DEGRADED_TO_REVIEWER.name())
.target(Map.of("dossierId", dossierRequest.getDossierId()))
.build()));
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(updatedDossier, httpHeaders, HttpStatus.OK);
} else {
Dossier created = createNewDossier(dossierRequest, ownerId, members, approvers);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(created.getId())
.category(AuditCategory.DOSSIER.name())
.message("Dossier has been created.")
.build());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>(created, httpHeaders, HttpStatus.CREATED);
}
}
private String getAndValidateOwnerId(String ownerId) {
String currentUserId = KeycloakSecurity.getUserId();
// quick check if self, just proceed
if (currentUserId.equals(ownerId)) {
return currentUserId;
}
String actualOwnerId = ownerId == null ? currentUserId : ownerId;
var user = userService.getUserById(actualOwnerId);
if (user.isEmpty()) {
userService.removeDeletedUsers(Collections.singleton(ownerId));
actualOwnerId = null;
}
// check he has a manager role, thus he can be the owner
if (user.isPresent() && user.get().getRoles().stream().noneMatch(ApplicationRoles.RED_MANAGER_ROLE::equals)) {
throw new BadRequestException("Make sure provided user id has the manager role.");
}
return actualOwnerId;
}
private Set<String> getAndValidateMembers(String ownerId, Set<String> memberIds) {
Set<String> actualMemberIds = memberIds == null ? new TreeSet<>() : memberIds;
if (actualMemberIds.stream().anyMatch(Objects::isNull)) {
throw new BadRequestException("Member IDs cannot be null");
}
// add owner automatically
if (ownerId != null) {
actualMemberIds.add(ownerId);
}
List<User> users = userService.getUsersByIds(actualMemberIds);
if (users.size() != actualMemberIds.size()) {
Set<String> deletedUserIds = userService.removeDeletedUsers(actualMemberIds);
actualMemberIds.removeAll(deletedUserIds);
}
if (users.stream().anyMatch(u -> u.getRoles().stream().noneMatch(VALID_MEMBER_ROLES::contains))) {
throw new BadRequestException("Make sure each provided member id has the permission to be a member of a dossier.");
}
return actualMemberIds;
}
private void checkValidityForArchivedDossierUpdateRequest(DossierRequest updatedDossier, Dossier existingDossier) {
//these must not be editted for archived dossiers
checkEquality(updatedDossier.getDossierTemplateId(), existingDossier.getDossierTemplateId(), "dossier template id");
checkEquality(updatedDossier.getDossierName(), existingDossier.getDossierName(), "dossier name");
checkEquality(updatedDossier.getDescription(), existingDossier.getDescription(), "the description");
checkEquality(updatedDossier.getDueDate(), existingDossier.getDueDate(), "the due date");
checkEquality(updatedDossier.getDossierStatusId(), existingDossier.getDossierStatusId(), "dossier status id");
}
private void updateFileStatusForDossierFiles(String dossierId, Collection<String> members) {
fileStatusManagementService.getDossierStatus(dossierId).stream().filter(fileStatus -> !fileStatus.isSoftOrHardDeleted()).forEach(f -> {
if (f.getAssignee() != null && !members.contains(f.getAssignee())) {
fileStatusManagementService.setCurrentFileAssignee(dossierId, f.getId(), null);
}
});
}
private Dossier createNewDossier(DossierRequest dossier, String ownerId, Set<String> members, Set<String> approvers) {
Dossier newDossier = dossierManagementService.addDossier(CreateOrUpdateDossierRequest.builder()
.dossierName(dossier.getDossierName())
.description(dossier.getDescription())
.dossierTemplateId(dossier.getDossierTemplateId())
.downloadFileTypes(dossier.getDownloadFileTypes())
.dueDate(dossier.getDueDate())
.reportTemplateIds(dossier.getReportTemplateIds() != null ? new ArrayList<>(dossier.getReportTemplateIds()) : Lists.newArrayList())
.watermarkId(dossier.getWatermarkId())
.previewWatermarkId(dossier.getPreviewWatermarkId())
.dossierStatusId(dossier.getDossierStatusId())
.build());
dossierACLService.updateDossierACL(members, approvers, ownerId, newDossier.getId());
dossierACLService.enhanceDossierWithACLData(newDossier);
return newDossier;
}
private <T> void checkEquality(T updatedValue, T existingValue, String message) {
if (updatedValue == null && existingValue != null || updatedValue != null && existingValue == null) {
throw new BadRequestException(String.format("You cannot edit %s in an archived dossier.", message));
}
if (updatedValue == null && existingValue == null) {
return;
}
if (!updatedValue.equals(existingValue)) {
throw new BadRequestException(String.format("You cannot edit %s in an archived dossier.", message));
}
}
@PreAuthorize("hasAuthority('" + DELETE_DOSSIER + "') && hasPermission(#dossierId, 'Dossier', 'ACCESS_OBJECT')")
public void deleteDossier(@PathVariable(DOSSIER_ID_PARAM) String dossierId) {
var dossierToBeDeleted = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, true, false));
dossierManagementService.delete(dossierId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier moved to trash.")
.build());
dossierToBeDeleted.getMemberIds()
.stream()
.filter(m -> !KeycloakSecurity.getUserId().equals(m))
.forEach(member -> notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(member)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOSSIER_DELETED.name())
.target(Map.of("dossierId", dossierId, "dossierName", dossierToBeDeleted.getDossierName()))
.build()));
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostAuthorize("hasPermission(#dossierId, 'Dossier', 'VIEW_OBJECT')")
public Dossier getDossier(@PathVariable(DOSSIER_ID_PARAM) String dossierId,
@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_DELETED_PARAM, defaultValue = "false", required = false) boolean includeDeleted) {
return dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, includeArchived, includeDeleted));
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getDossiers(@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_DELETED_PARAM, defaultValue = "false", required = false) boolean includeDeleted) {
return dossierManagementService.getAllDossiers(includeArchived, includeDeleted).stream().map(dossierACLService::enhanceDossierWithACLData).collect(Collectors.toList());
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getDossiersForDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
@RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
@RequestParam(name = INCLUDE_DELETED_PARAM, defaultValue = "false", required = false) boolean includeDeleted) {
return dossierManagementService.getAllDossiersForDossierTemplateId(dossierTemplateId, includeArchived, includeDeleted)
.stream()
.map(dossierACLService::enhanceDossierWithACLData)
.collect(Collectors.toList());
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getSoftDeletedDossiers() {
return dossierManagementService.getSoftDeletedDossiers().stream().map(dossierACLService::enhanceDossierWithACLData).collect(Collectors.toList());
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getArchivedDossiers() {
return dossierManagementService.getArchivedDossiers().stream().map(dossierACLService::enhanceDossierWithACLData).collect(Collectors.toList());
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PostFilter("hasPermission(filterObject.id, 'Dossier', 'VIEW_OBJECT')")
public List<Dossier> getArchivedDossiersForDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId) {
return dossierManagementService.getArchivedDossiersForDossierTemplateId(dossierTemplateId)
.stream()
.map(dossierACLService::enhanceDossierWithACLData)
.collect(Collectors.toList());
}
@PreAuthorize("hasAuthority('" + ARCHIVE_DOSSIER + "')")
@PreFilter("hasPermission(filterObject, 'Dossier', 'ACCESS_OBJECT')")
public void archiveDossiers(@RequestBody Set<String> dossierIds) {
for (String dossierId : dossierIds) {
accessControlService.verifyUserIsDossierOwner(dossierId);
}
dossierManagementService.archiveDossiers(dossierIds);
for (String dossierId : dossierIds) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier archived.")
.build());
}
}
@PreAuthorize("hasAuthority('" + UNARCHIVE_DOSSIER + "')")
public void unarchiveDossiers(@RequestBody Set<String> dossierIds) {
dossierManagementService.unarchiveDossiers(dossierIds);
for (String dossierId : dossierIds) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier restored from archive.")
.build());
}
}
@PreAuthorize("hasAuthority('" + DELETE_DOSSIER + "')")
@PreFilter("hasPermission(filterObject, 'Dossier', 'ACCESS_OBJECT')")
public void hardDeleteDossiers(@RequestParam(DOSSIER_ID_PARAM) Set<String> dossierIds) {
for (String dossierId : dossierIds) {
accessControlService.verifyUserIsDossierOwner(dossierId);
}
dossierManagementService.hardDeleteDossiers(dossierIds);
for (String dossierId : dossierIds) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier permanently deleted.")
.build());
}
}
@PreAuthorize("hasAuthority('" + DELETE_DOSSIER + "')")
@PreFilter("hasPermission(filterObject, 'Dossier', 'ACCESS_OBJECT')")
public void undeleteDossiers(@RequestBody Set<String> dossierIds) {
dossierManagementService.undeleteDossiers(dossierIds);
for (String dossierId : dossierIds) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Dossier restored from trash.")
.build());
}
}
}

View File

@ -0,0 +1,47 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierStatsService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DossierStatsResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierStats;
import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class DossierStatsController implements DossierStatsResource {
private final DossierStatsService dossierStatsService;
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "') && hasPermission(#dossierId, 'Dossier', 'VIEW_OBJECT')")
public DossierStats getDossierStats(@PathVariable(DOSSIER_ID_PARAM) String dossierId) {
return dossierStatsService.getDossierStats(dossierId);
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
@PreFilter("hasPermission(filterObject, 'Dossier', 'ACCESS_OBJECT')")
public List<DossierStats> getDossierStats(@RequestBody Set<String> dossierIds) {
return dossierIds.stream().map(dossierStatsService::getDossierStats).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,105 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER_STATUS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_DOSSIER_STATUS;
import java.util.List;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.ColorUtils;
import com.iqser.red.service.persistence.management.v1.processor.utils.DossierStatusMapper;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DossierStatusResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierStatusRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierStatusRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierStatusInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class DossierStatusController implements DossierStatusResource {
private final DossierStatusPersistenceService dossierStatusPersistenceService;
@Override
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_STATUS + "')")
public DossierStatusInfo createOrUpdateDossierStatus(@RequestBody DossierStatusRequest dossierStatusRequest) {
if (dossierStatusRequest.getDossierTemplateId() == null) {
throw new BadRequestException("Dossier Template must be set for creation");
}
if (dossierStatusRequest.getName() == null || dossierStatusRequest.getName().isEmpty()) {
throw new BadRequestException("Dossier status name must be set");
}
//validate color
ColorUtils.validateColor(dossierStatusRequest.getColor());
//validate rank
if (dossierStatusRequest.getRank() < 0) {
throw new BadRequestException("The rank must not be negative");
}
var response = dossierStatusPersistenceService.createOrUpdateDossierStatus(CreateOrUpdateDossierStatusRequest.builder()
.dossierStatusId(dossierStatusRequest.getDossierStatusId())
.name(dossierStatusRequest.getName())
.description(dossierStatusRequest.getDescription())
.dossierTemplateId(dossierStatusRequest.getDossierTemplateId())
.rank(dossierStatusRequest.getRank())
.color(dossierStatusRequest.getColor())
.build());
return MagicConverter.convert(response, DossierStatusInfo.class);
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')")
public List<DossierStatusInfo> getAllDossierStatusForTemplate(@PathVariable("dossierTemplateId") String dossierTemplateId) {
return dossierStatusPersistenceService.getAllDossierStatusForTemplate(dossierTemplateId)
.stream()
.map(d -> MagicConverter.convert(d, DossierStatusInfo.class))
.collect(Collectors.toList());
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')")
public List<DossierStatusInfo> getAllDossierStatuses(@RequestBody List<String> dossierTemplateIds) {
return dossierStatusPersistenceService.getAllDossierStatuses(dossierTemplateIds)
.stream()
.map(d -> MagicConverter.convert(d, DossierStatusInfo.class))
.collect(Collectors.toList());
}
@Override
@Transactional
@PreAuthorize("hasAuthority('" + READ_DOSSIER_STATUS + "')")
public DossierStatusInfo getDossierStatus(@PathVariable("dossierStatusId") String dossierStatusId) {
return MagicConverter.convert(dossierStatusPersistenceService.getDossierStatus(dossierStatusId), DossierStatusInfo.class, new DossierStatusMapper());
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_STATUS + "')")
public void deleteDossierStatus(@PathVariable("dossierStatusId") String dossierStatusId,
@RequestParam(value = DOSSIER_STATUS_REPLACE_ID, required = false) String replaceDossierStatusId) {
dossierStatusPersistenceService.deleteDossierStatus(dossierStatusId, replaceDossierStatusId);
}
}

View File

@ -0,0 +1,307 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER_TEMPLATES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_DOSSIER_TEMPLATES;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateStatsService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DossierTemplateResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DownloadResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.CloneDossierTemplateRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.CreateOrUpdateDossierTemplateRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplate;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateStats;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.importexport.ExportDownloadRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.importexport.ImportDossierTemplateRequest;
import feign.FeignException;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class DossierTemplateController implements DossierTemplateResource {
private final DossierTemplateManagementService dossierTemplateManagementService;
private final DossierTemplateStatsService dossierTemplateStatsService;
private final AuditPersistenceService auditPersistenceService;
private final DossierManagementService dossierManagementService;
private final DossierACLService dossierACLService;
@Override
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_TEMPLATES + "')")
public DossierTemplateModel createOrUpdateDossierTemplate(@RequestBody DossierTemplateModel dossierTemplateModel) {
String userId = KeycloakSecurity.getUserId();
dossierTemplateModel.setCreatedBy(userId);
dossierTemplateModel.setModifiedBy(userId);
var dossierTemplate = new CreateOrUpdateDossierTemplateRequest();
BeanUtils.copyProperties(dossierTemplateModel, dossierTemplate);
try {
DossierTemplateModel response = convert(dossierTemplateManagementService.createOrUpdateDossierTemplate(dossierTemplate));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(response.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier Template has been added or updated")
.build());
return response;
} catch (FeignException e) {
throw processFeignException(e);
}
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
public List<DossierTemplateModel> getAllDossierTemplates() {
return dossierTemplateManagementService.getAllDossierTemplates().stream().map(this::convert).collect(Collectors.toList());
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
public DossierTemplateModel getDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {
try {
return convert(dossierTemplateManagementService.getDossierTemplate(dossierTemplateId));
} catch (FeignException e) {
throw processFeignException(e);
}
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
public void deleteDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {
String userId = KeycloakSecurity.getUserId();
List<Dossier> dossiers = dossierManagementService.getAllDossiers(true, false);
if (dossiers != null && dossiers.stream().anyMatch(dossier -> dossier.getDossierTemplateId().equals(dossierTemplateId))) {
throw new ConflictException("Can not delete dossier template because there are dossiers based on it");
}
dossierTemplateManagementService.deleteDossierTemplate(dossierTemplateId, userId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier Template has been deleted")
.build());
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
public void deleteDossierTemplates(@RequestBody List<String> dossierTemplateIds) {
String userId = KeycloakSecurity.getUserId();
List<String> errorIds = new ArrayList<>();
for (String dossierTemplateId : dossierTemplateIds) {
try {
List<Dossier> dossiers = dossierManagementService.getAllDossiers(true, false);
if (dossiers != null && dossiers.stream().anyMatch(dossier -> dossier.getDossierTemplateId().equals(dossierTemplateId))) {
throw new ConflictException("Can not delete dossier template because there are dossiers based on it");
}
dossierTemplateManagementService.deleteDossierTemplate(dossierTemplateId, userId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier template has been deleted")
.build());
} catch (FeignException e) {
errorIds.add(dossierTemplateId);
}
}
if (!errorIds.isEmpty()) {
throw new BadRequestException("Failed to delete dossier templates with ids: " + errorIds);
}
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_TEMPLATES + "')")
public DossierTemplateModel cloneDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId,
@RequestBody CloneDossierTemplateRequest cloneDossierTemplateRequest) {
String userId = KeycloakSecurity.getUserId();
try {
DossierTemplateModel response = convert(dossierTemplateManagementService.cloneDossierTemplate(dossierTemplateId, cloneDossierTemplateRequest));
auditPersistenceService.audit(AuditRequest.builder()
.userId(userId)
.objectId(response.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier Template has been cloned")
.build());
return response;
} catch (FeignException e) {
throw processFeignException(e);
}
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
public DossierTemplateStats getDossierTemplateStats(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {
try {
var stats = dossierTemplateStatsService.getDossierTemplateStats(dossierTemplateId);
enhanceDossierTemplateStatsWithACLMemberDetails(stats);
return stats;
} catch (FeignException e) {
throw processFeignException(e);
}
}
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
public List<DossierTemplateStats> getDossierTemplateStats() {
try {
var statsList = dossierTemplateStatsService.getDossierTemplateStats();
statsList.forEach(this::enhanceDossierTemplateStatsWithACLMemberDetails);
return statsList;
} catch (FeignException e) {
throw processFeignException(e);
}
}
@PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
public DownloadResponse prepareExportDownload(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {
try {
ExportDownloadRequest request = ExportDownloadRequest.builder().dossierTemplateId(dossierTemplateId).userId(KeycloakSecurity.getUserId()).build();
var response = dossierTemplateManagementService.prepareExportDownload(request);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(response.getValue())
.category(AuditCategory.DOWNLOAD.name())
.message("Export Download was prepared")
.details(Map.of("dossierTemplateId", request.getDossierTemplateId()))
.build());
return new DownloadResponse(response.getValue());
} catch (FeignException e) {
throw processFeignException(e);
}
}
@Timed
@PreAuthorize("hasAuthority('" + WRITE_DOSSIER_TEMPLATES + "')")
public DossierTemplateModel importDossierTemplate(@RequestPart(name = "file") MultipartFile file,
@RequestParam(value = DOSSIER_TEMPLATE_ID, required = false) String dossierTemplateId,
@RequestParam(value = "updateExistingDossierTemplate", required = false, defaultValue = "false") boolean updateExistingDossierTemplate) {
String originalFileName = file.getOriginalFilename();
if (originalFileName == null || originalFileName.isEmpty()) {
throw new BadRequestException("Could not upload file, no filename provided.");
}
var extension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1).toLowerCase();
if ("zip".equalsIgnoreCase(extension)) {
if (StringUtils.isEmpty(dossierTemplateId) && updateExistingDossierTemplate) {
throw new BadRequestException("Could not update with dossier template empty");
}
try {
if (dossierTemplateId != null && updateExistingDossierTemplate) {
dossierTemplateManagementService.getDossierTemplate(dossierTemplateId); //check if the dossierTemplate to update exists
}
ImportDossierTemplateRequest request = ImportDossierTemplateRequest.builder()
.dossierTemplateId(dossierTemplateId)
.updateExistingDossierTemplate(updateExistingDossierTemplate)
.userId(KeycloakSecurity.getUserId())
.archive(file.getBytes())
.build();
DossierTemplate loadedDossierTemplate = dossierTemplateManagementService.importDossierTemplate(request);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(loadedDossierTemplate.getId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Dossier template was imported")
.details(Map.of("dossierTemplateId", loadedDossierTemplate.getId()))
.build());
return convert(loadedDossierTemplate);
} catch (IOException e) {
throw new BadRequestException(e.getMessage(), e);
} catch (FeignException e) {
throw processFeignException(e);
}
} else {
throw new BadRequestException("Invalid extension");
}
}
private void enhanceDossierTemplateStatsWithACLMemberDetails(DossierTemplateStats stats) {
Set<String> members = new HashSet<>();
stats.getDossiersInTemplate().forEach(d -> members.addAll(dossierACLService.getMembers(d)));
stats.setNumberOfPeople(members.size());
}
private DossierTemplateModel convert(DossierTemplate dossierTemplate) {
return DossierTemplateModel.builder()
.dossierTemplateId(dossierTemplate.getId())
.name(dossierTemplate.getName())
.description(dossierTemplate.getDescription())
.dateAdded(dossierTemplate.getDateAdded())
.dateModified(dossierTemplate.getDateModified())
.createdBy(dossierTemplate.getCreatedBy())
.modifiedBy(dossierTemplate.getModifiedBy())
.validFrom(dossierTemplate.getValidFrom())
.validTo(dossierTemplate.getValidTo())
.downloadFileTypes(dossierTemplate.getDownloadFileTypes())
.dossierTemplateStatus(DossierTemplateStatus.valueOf(dossierTemplate.getDossierTemplateStatus().name()))
.build();
}
}

View File

@ -0,0 +1,33 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER_TEMPLATES;
import java.util.List;
import java.util.Set;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateStatsService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DossierTemplateStatsResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateDictionaryStats;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class DossierTemplateStatsController implements DossierTemplateStatsResource {
private final DossierTemplateStatsService dossierTemplateStatsService;
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
public List<DossierTemplateDictionaryStats> getDossierTemplateStats(Set<String> dossierTemplateIds) {
return dossierTemplateStatsService.getDossierTemplateStats(dossierTemplateIds);
}
}

View File

@ -0,0 +1,283 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.PROCESS_DOWNLOAD;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOWNLOAD_STATUS;
import static com.iqser.red.service.persistence.management.v1.processor.utils.DownloadBufferUtils.fileProxyStreamForDownload;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.DownloadService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DownloadResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DownloadResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DownloadStatusResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.PrepareDownloadRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.PrepareDownloadWithOptionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RemoveDownloadRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DownloadFileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.download.DownloadRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.download.DownloadStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.download.DownloadWithOptionRequest;
import com.iqser.red.storage.commons.service.StorageService;
import com.iqser.red.persistence.service.v1.external.api.impl.service.OneTimeTokenService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class DownloadController implements DownloadResource {
private static final Pattern COLOR_PATTERN = Pattern.compile("^#[\\da-f]{6,6}$");
private final DossierManagementService dossierService;
private final FileStatusService fileStatusService;
private final DownloadService downloadService;
private final StorageService storageService;
private final AuditPersistenceService auditPersistenceService;
private final OneTimeTokenService oneTimeTokenDownloadService;
private final AccessControlService accessControlService;
@Value("${storage.backend:s3}")
private String storageBackend;
@PreAuthorize("hasAuthority('" + PROCESS_DOWNLOAD + "')")
public DownloadResponse prepareDownload(@RequestBody PrepareDownloadRequest request) {
// check the user is non-member or reviewer
accessControlService.verifyUserIsDossierOwnerOrApprover(request.getDossierId());
var response = downloadService.prepareDownload(convert(request));
auditPersistenceService.insertRecord(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(response.getValue())
.category(AuditCategory.DOWNLOAD.name())
.message("Download was prepared")
.details(Map.of("dossierId", request.getDossierId()))
.build());
return new DownloadResponse(response.getValue());
}
@PreAuthorize("hasAuthority('" + PROCESS_DOWNLOAD + "')")
public DownloadResponse prepareDownload(@RequestBody PrepareDownloadWithOptionRequest request) {
validateDossierId(request.getDossierId());
validateAndFilterFileIds(request);
if ((request.getDownloadFileTypes() == null || request.getDownloadFileTypes().isEmpty()) && (request.getReportTemplateIds() == null || request.getReportTemplateIds()
.isEmpty())) {
throw new BadRequestException("Download and report types cannot both be empty");
}
if (request.getRedactionPreviewColor() != null && !COLOR_PATTERN.matcher(request.getRedactionPreviewColor()).matches()) {
throw new BadRequestException("The specified redaction-preview-color is malformed.");
}
var response = downloadService.prepareDownload(convert(request));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(response.getValue())
.category(AuditCategory.DOWNLOAD.name())
.message("Download was prepared")
.details(Map.of("dossierId", request.getDossierId()))
.build());
return new DownloadResponse(response.getValue());
}
private void validateDossierId(String dossierId) {
if (StringUtils.isBlank(dossierId)) {
throw new BadRequestException("Empty dossier id");
}
dossierService.getDossierById(dossierId, true, true);
accessControlService.verifyUserIsDossierOwnerOrApprover(dossierId);
}
private void validateAndFilterFileIds(PrepareDownloadWithOptionRequest request) {
List<FileModel> validFiles = fileStatusService.getDossierStatus(request.getDossierId());
var fileIds = request.getFileIds();
if (fileIds != null && !fileIds.isEmpty()) { // validate the ids provided
validFiles = validFiles.stream().filter(f -> fileIds.contains(f.getId())).collect(Collectors.toList());
if (validFiles.isEmpty()) {
throw new NotFoundException("No file id provided is found");
}
} // otherwise consider the files from dossier
var validFilesAndNotProcessed = validFiles.stream().filter(f -> !(f.getAnalysisVersion() > 0 && f.getNumberOfAnalyses() > 0)).collect(Collectors.toList());
if (!validFilesAndNotProcessed.isEmpty()) {
throw new BadRequestException("At least a file is in its initial analysis process");
}
request.setFileIds(validFiles.stream().map(FileModel::getId).collect(Collectors.toList()));
var approvedFiles = validFiles.stream()
.filter(f -> f.getWorkflowStatus().equals(WorkflowStatus.APPROVED))
.toList();
// special corner case: unapproved files, no reports and only REDACTED type selected
if (approvedFiles.isEmpty() && (request.getReportTemplateIds() == null || request.getReportTemplateIds()
.isEmpty()) && request.getDownloadFileTypes() != null && request.getDownloadFileTypes().size() == 1 && request.getDownloadFileTypes()
.contains(DownloadFileType.REDACTED)) {
throw new BadRequestException("Unapproved files in redacted state with no reports cannot be included");
}
}
private DownloadWithOptionRequest convert(PrepareDownloadWithOptionRequest request) {
return DownloadWithOptionRequest.builder()
.dossierId(request.getDossierId())
.userId(KeycloakSecurity.getUserId())
.fileIds(request.getFileIds())
.downloadFileTypes(request.getDownloadFileTypes())
.reportTemplateIds(request.getReportTemplateIds())
.redactionPreviewColor(request.getRedactionPreviewColor())
.build();
}
@Override
@PreAuthorize("hasAuthority('" + PROCESS_DOWNLOAD + "')")
public void deleteDownloadStatus(@RequestBody RemoveDownloadRequest removeDownloadRequest) {
removeDownloadRequest.getStorageIds().forEach(storageId -> {
downloadService.deleteDownloadStatus(JSONPrimitive.of(storageId));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(storageId)
.category(AuditCategory.DOWNLOAD.name())
.message("Remove Prepared Download")
.build());
});
}
@PreAuthorize("hasAuthority('" + READ_DOWNLOAD_STATUS + "')")
public DownloadStatusResponse getDownloadStatus() {
var resp = downloadService.getDownloadStatus(KeycloakSecurity.getUserId());
return new DownloadStatusResponse(resp);
}
@PreAuthorize("hasAuthority('" + PROCESS_DOWNLOAD + "')")
public ResponseEntity<FileSystemResource> downloadFile(@RequestParam(STORAGE_ID) String storageId,
@RequestParam(value = "inline", required = false, defaultValue = FALSE) boolean inline) {
var userId = KeycloakSecurity.getUserId();
var downloadStatus = getDownloadStatus(storageId, userId);
var fileDownloadStream = getFileForDownload(storageId, userId);
return getResponseEntity(inline, fileDownloadStream, downloadStatus.getFilename(), MediaType.parseMediaType("application/zip"));
}
private DownloadStatus getDownloadStatus(String storageId, String userId) {
// TODO Add endpoint to get single download status for userId and storageId.
var downloadStatusResponse = downloadService.getDownloadStatus(userId);
Optional<DownloadStatus> downloadStatusOptional = downloadStatusResponse.stream().filter(ds -> ds.getStorageId().equals(storageId)).findFirst();
return downloadStatusOptional.orElseThrow(() -> new NotFoundException("Download status not found for this user"));
}
private InputStreamResource getFileForDownload(String storageId, String userId) {
try {
var response = storageService.getObject(storageId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(userId)
.objectId(storageId)
.category(AuditCategory.DOWNLOAD.name())
.message("File was downloaded.")
.build());
downloadService.setDownloaded(JSONPrimitive.of(storageId));
return response;
} catch (Exception e) {
throw new NotFoundException(e.getMessage(), e);
}
}
@SneakyThrows
private ResponseEntity<FileSystemResource> getResponseEntity(boolean inline, InputStreamResource resource, String filename, MediaType mediaType) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(mediaType);
if (filename != null) {
httpHeaders.add("Content-Disposition", inline ? "inline" : "attachment" + "; filename*=utf-8''" + StringEncodingUtils.urlEncode(filename));
}
return new ResponseEntity<>(fileProxyStreamForDownload(resource.getInputStream()), httpHeaders, HttpStatus.OK);
}
@Override
@PreAuthorize("hasAuthority('" + PROCESS_DOWNLOAD + "')")
public JSONPrimitive<String> generateOneTimeToken(@RequestBody JSONPrimitive<String> storageIdWrapper) {
log.debug("Generate one time token");
return JSONPrimitive.of(oneTimeTokenDownloadService.createToken(storageIdWrapper.getValue(), KeycloakSecurity.getUserId()).getTokenId());
}
@Override
public ResponseEntity<FileSystemResource> downloadFileUsingOTT(@PathVariable(OTT) String oneTimeToken,
@RequestParam(value = "inline", required = false, defaultValue = FALSE) boolean inline) {
log.debug("downloadFileUsingOTT '{}'", oneTimeToken);
var token = oneTimeTokenDownloadService.getToken(oneTimeToken);
var downloadStatus = getDownloadStatus(token.getStorageId(), token.getUserId());
var fileDownloadStream = getFileForDownload(token.getStorageId(), token.getUserId());
return getResponseEntity(inline, fileDownloadStream, downloadStatus.getFilename(), MediaType.parseMediaType("application/zip"));
}
private DownloadRequest convert(PrepareDownloadRequest request) {
return DownloadRequest.builder().dossierId(request.getDossierId()).userId(KeycloakSecurity.getUserId()).fileIds(request.getFileIds()).build();
}
}

View File

@ -0,0 +1,134 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import java.time.OffsetDateTime;
import java.util.stream.Collectors;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.iqser.red.commons.spring.ErrorMessage;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ExternalControllerAdvice {
@ResponseBody
@ResponseStatus(value = HttpStatus.NOT_FOUND)
@ExceptionHandler(value = NotFoundException.class)
public ErrorMessage handleContentNotFoundException(NotFoundException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
/* error handling */
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = BadRequestException.class)
public ErrorMessage handleBadRequestException(BadRequestException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@ResponseBody
@ResponseStatus(value = HttpStatus.CONFLICT)
@ExceptionHandler(value = {ConflictException.class})
protected ErrorMessage handleConflictException(ConflictException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@ResponseBody
@ResponseStatus(value = HttpStatus.FORBIDDEN)
@ExceptionHandler({AccessDeniedException.class})
public ErrorMessage handleAccessDeniedException(AccessDeniedException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@ResponseBody
@ResponseStatus(value = HttpStatus.FORBIDDEN)
@ExceptionHandler({NotAllowedException.class})
public ErrorMessage handleNotAllowedException(NotAllowedException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler({MethodArgumentNotValidException.class})
public ErrorMessage handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
var errorList = e.getFieldErrors();
String errorListAsString = errorList.stream().map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()).collect(Collectors.joining(", "));
return new ErrorMessage(OffsetDateTime.now(), String.format("You have empty/wrong formatted parameters: %s", errorListAsString));
}
@ResponseBody
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler({MissingServletRequestPartException.class})
public ErrorMessage handleMissingServletRequestPartException(MissingServletRequestPartException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({HttpMessageNotReadableException.class})
public ErrorMessage handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
var cause = e.getCause();
if (cause instanceof InvalidFormatException) {
InvalidFormatException invalidFormatException = (InvalidFormatException) cause;
Class<?> targetType = invalidFormatException.getTargetType();
if (targetType != null && targetType.isEnum()) {
return new ErrorMessage(OffsetDateTime.now(), String.format("Unsupported value for %s", targetType.getSimpleName()));
}
return new ErrorMessage(OffsetDateTime.now(), cause.getMessage());
}
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@Order(10000)
public static class BinderControllerAdvice {
@InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
// This code protects Spring Core from a "Remote Code Execution" attack (dubbed "Spring4Shell").
// By applying this mitigation, you prevent the "Class Loader Manipulation" attack vector from firing.
// For more details, see this post: https://www.lunasec.io/docs/blog/spring-rce-vulnerabilities/
String[] denylist = new String[]{"class.*", "Class.*", "*.class.*", "*.Class.*"};
dataBinder.setDisallowedFields(denylist);
}
}
}

View File

@ -0,0 +1,167 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_FILE_ATTRIBUTES_CONFIG;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_FILE_ATTRIBUTES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_FILE_ATTRIBUTES_CONFIG;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.entity.configuration.FileAttributesGeneralConfigurationEntity;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileAttributeConfigEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileAttributesManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.FileAttributesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttributes;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttributesConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.FileAttributesGeneralConfiguration;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileAttributeConfig;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class FileAttributesController implements FileAttributesResource {
private final FileAttributesManagementService fileAttributesManagementService;
private final FileAttributeConfigPersistenceService fileAttributeConfigPersistenceService;
private final AuditPersistenceService auditPersistenceService;
private final FileStatusService fileStatusService;
private final AccessControlService accessControlService;
@Override
@PreAuthorize("hasAuthority('" + WRITE_FILE_ATTRIBUTES_CONFIG + "')")
public FileAttributesConfig setFileAttributesConfig(String dossierTemplateId, FileAttributesConfig fileAttributesConfig) {
if (StringUtils.isEmpty(fileAttributesConfig.getEncoding()) || !encodingList.contains(fileAttributesConfig.getEncoding().trim())) {
throw new BadRequestException("Invalid encoding setting");
}
fileAttributeConfigPersistenceService.setFileAttributesGeneralConfig(dossierTemplateId,
MagicConverter.convert(fileAttributesConfig, FileAttributesGeneralConfigurationEntity.class));
var result = fileAttributeConfigPersistenceService.setFileAttributesConfig(dossierTemplateId,
MagicConverter.convert(fileAttributesConfig.getFileAttributeConfigs(), FileAttributeConfigEntity.class));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Changed file attributes base configuration & attribute configuration ( CSV Import )")
.build());
return FileAttributesConfig.builder()
.filenameMappingColumnHeaderName(fileAttributesConfig.getFilenameMappingColumnHeaderName())
.delimiter(fileAttributesConfig.getDelimiter())
.encoding(fileAttributesConfig.getEncoding())
.fileAttributeConfigs(MagicConverter.convert(result, FileAttributeConfig.class))
.build();
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_FILE_ATTRIBUTES_CONFIG + "')")
public FileAttributeConfig addOrUpdateFileAttribute(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody FileAttributeConfig fileAttribute) {
var result = fileAttributeConfigPersistenceService.addOrUpdateFileAttribute(dossierTemplateId, MagicConverter.convert(fileAttribute, FileAttributeConfigEntity.class));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("File attributes added/updated")
.details(Map.of("FileAttributeName", fileAttribute.getLabel() != null ? fileAttribute.getLabel() : "", "dossierTemplateId", dossierTemplateId))
.build());
return MagicConverter.convert(result, FileAttributeConfig.class);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_FILE_ATTRIBUTES_CONFIG + "')")
public void deleteFileAttribute(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @PathVariable(FILE_ATTRIBUTE_ID) String fileAttributeId) {
fileAttributeConfigPersistenceService.deleteFileAttribute(fileAttributeId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("File attributes removed")
.details(Map.of("FileAttributeId", fileAttributeId))
.build());
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_FILE_ATTRIBUTES_CONFIG + "')")
public void deleteFileAttributes(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody List<String> fileAttributeIds) {
fileAttributeConfigPersistenceService.deleteFileAttributes(fileAttributeIds);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("File attributes removed")
.details(Map.of("FileAttributeId", fileAttributeIds))
.build());
}
@PreAuthorize("hasAuthority('" + READ_FILE_ATTRIBUTES_CONFIG + "')")
public FileAttributesConfig getFileAttributesConfiguration(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {
var fileAttributeConfigs = fileAttributeConfigPersistenceService.getFileAttributes(dossierTemplateId);
FileAttributesGeneralConfiguration generalConfig = new FileAttributesGeneralConfiguration();
try {
generalConfig = MagicConverter.convert(fileAttributeConfigPersistenceService.getFileAttributesGeneralConfiguration(dossierTemplateId),
FileAttributesGeneralConfiguration.class);
} catch (Exception e) {
log.debug("No general config defined", e);
}
return FileAttributesConfig.builder()
.filenameMappingColumnHeaderName(generalConfig.getFilenameMappingColumnHeaderName())
.delimiter(generalConfig.getDelimiter())
.encoding(generalConfig.getEncoding())
.fileAttributeConfigs(MagicConverter.convert(fileAttributeConfigs, FileAttributeConfig.class))
.build();
}
@PreAuthorize("hasAuthority('" + WRITE_FILE_ATTRIBUTES + "')")
public void setFileAttributes(@PathVariable(DOSSIER_ID_PARAM) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody FileAttributes fileAttributes) {
var file = fileStatusService.getStatus(fileId);
if (file.getWorkflowStatus().equals(WorkflowStatus.APPROVED)) {
throw new NotAllowedException("File is approved. File attributes are not editable anymore.");
}
if (file.getWorkflowStatus().equals(WorkflowStatus.UNDER_APPROVAL)) {
accessControlService.verifyUserIsApprover(dossierId);
}
accessControlService.verifyUserIsMemberOrApprover(dossierId);
fileAttributesManagementService.setFileAttributes(dossierId, fileId, fileAttributes.getAttributeIdToValue());
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("File attributes has been edited for a document.")
.build());
}
}

View File

@ -0,0 +1,238 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_FILE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DOWNLOAD_ORIGINAL_FILE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.ROTATE_PAGE;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import static com.iqser.red.service.persistence.management.v1.processor.utils.DownloadBufferUtils.fileProxyStreamForDownload;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Sets;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.client.pdftronredactionservice.PDFTronClient;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.StorageIdUtils;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.service.v1.api.external.resource.FileManagementResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RotatePagesRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.storage.commons.service.StorageService;
import feign.FeignException;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class FileManagementController implements FileManagementResource {
private static final String DOWNLOAD_HEADER_NAME = "Content-Disposition";
private final FileService fileService;
private final AuditPersistenceService auditPersistenceService;
private final AccessControlService accessControlService;
private final PDFTronClient pdfTronClient;
private final ReanalysisService reanalysisService;
private final StorageService storageService;
private final FileStatusManagementService fileStatusManagementService;
@Timed
@Override
@PreAuthorize("hasAuthority('" + DELETE_FILE + "')")
public void deleteFile(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
fileService.deleteFile(dossierId, fileId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("File has been deleted.")
.details(Map.of("fileId", fileId))
.build());
}
@Timed
@Override
@PreAuthorize("hasAuthority('" + DELETE_FILE + "')")
public void deleteFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody List<String> fileIds) {
List<String> errorIds = new ArrayList<>();
for (String fileId : fileIds) {
try {
fileService.deleteFile(dossierId, fileId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Files have been deleted.")
.details(Map.of("Size", fileIds.size()))
.build());
} catch (Exception e) {
errorIds.add(fileId);
}
}
if (!errorIds.isEmpty()) {
throw new BadRequestException("Failed to delete files with ids: " + errorIds);
}
}
@Timed
@Override
@PreAuthorize("hasAuthority('" + DOWNLOAD_ORIGINAL_FILE + "')")
public ResponseEntity<?> downloadOriginal(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "inline", required = false, defaultValue = FALSE) boolean inline) {
try {
var file = fileStatusManagementService.getFileStatus(fileId);
var untouchedFileStream = storageService.getObject(StorageIdUtils.getStorageId(dossierId, fileId, FileType.ORIGIN));
return getResponseEntity(inline, untouchedFileStream, file.getFilename(), MediaType.APPLICATION_PDF);
} catch (FeignException e) {
if (e.status() == HttpStatus.NOT_FOUND.value()) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
}
log.debug(e.getMessage(), e);
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@SneakyThrows
private ResponseEntity<?> getResponseEntity(boolean inline, InputStreamResource resource, String filename, MediaType mediaType) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(mediaType);
if (filename != null) {
httpHeaders.add(DOWNLOAD_HEADER_NAME, inline ? "inline" : "attachment" + "; filename*=utf-8''" + StringEncodingUtils.urlEncode(filename));
}
return new ResponseEntity<>(fileProxyStreamForDownload(resource.getInputStream()), httpHeaders, HttpStatus.OK);
}
@Override
@PreAuthorize("hasAuthority('" + DELETE_FILE + "')")
public void hardDeleteFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(FILE_IDS) Set<String> fileIds) {
for (String fileId : fileIds) {
if (fileStatusManagementService.getFileStatus(fileId).getAssignee() != null) {
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
}
}
fileService.hardDeleteFiles(dossierId, fileIds);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Files has been hard deleted.")
.details(Map.of("FileIds", fileIds))
.build());
}
@Override
@PreAuthorize("hasAuthority('" + DELETE_FILE + "')")
public void restoreFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody Set<String> fileIds) {
verifyUserIsDossierOwnerOrApproverOrAssignedReviewer(dossierId, fileIds);
fileService.undeleteFiles(dossierId, fileIds);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Files has been restored.")
.details(Map.of("FileIds", fileIds))
.build());
}
@Override
@PreAuthorize("hasAuthority('" + ROTATE_PAGE + "')")
public void rotatePages(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RotatePagesRequest rotatePagesRequest) {
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsReviewer(dossierId, fileId);
try {
pdfTronClient.rotate(com.iqser.red.service.pdftron.redaction.v1.api.model.RotatePagesRequest.builder()
.dossierId(dossierId)
.fileId(fileId)
.pages(rotatePagesRequest.getPages())
.build());
fileStatusManagementService.updateFileModificationDate(fileId);
FileModel fileModel = fileStatusManagementService.getFileStatus(fileId);
if (!fileModel.isExcludedFromAutomaticAnalysis()) {
if (fileModel.getOcrStartTime() != null || fileModel.getOcrEndTime() != null) {
reanalysisService.ocrFile(dossierId, fileId, true);
} else {
reanalysisService.reanalyzeFiles(dossierId, Sets.newHashSet(fileId), true);
}
}
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("Pages have been rotated.")
.details(Map.of("Pages", rotatePagesRequest.getPages().keySet()))
.build());
} catch (FeignException e) {
throw processFeignException(e);
}
}
private void verifyUserIsDossierOwnerOrApproverOrAssignedReviewer(String dossierId, Set<String> fileIds) {
try {
accessControlService.verifyUserIsDossierOwnerOrApprover(dossierId);
} catch (AccessDeniedException e1) {
try {
for (String fileId : fileIds) {
accessControlService.verifyUserIsReviewer(dossierId, fileId);
}
} catch (NotAllowedException e2) {
throw new NotAllowedException("User must be dossier owner, approver or assigned reviewer of the file.");
}
}
}
}

View File

@ -0,0 +1,40 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_GENERAL_CONFIGURATION;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_GENERAL_CONFIGURATION;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.service.v1.api.external.resource.GeneralSettingsResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.GeneralConfigurationModel;
import com.iqser.red.persistence.service.v1.external.api.impl.service.GeneralConfigurationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class GeneralSettingsController implements GeneralSettingsResource {
private final GeneralConfigurationService generalConfigurationService;
@Override
@PreAuthorize("hasAuthority('" + READ_GENERAL_CONFIGURATION + "')")
public GeneralConfigurationModel getGeneralConfigurations() {
return generalConfigurationService.getGeneralConfigurations();
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_GENERAL_CONFIGURATION + "')")
public void updateGeneralConfigurations(@RequestBody GeneralConfigurationModel generalConfigurationModel) {
generalConfigurationService.updateGeneralConfigurations(generalConfigurationModel);
}
}

View File

@ -0,0 +1,93 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.*;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import static com.iqser.red.service.persistence.management.v1.processor.utils.StorageIdUtils.getStorageId;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.Highlights;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.TextHighlightConversionOperation;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.TextHighlightConversionRequest;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.HighlightsResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AnnotationIds;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.DeleteImportedRedactionsRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.storage.commons.service.StorageService;
import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
@RestController
@RequiredArgsConstructor
public class HighlightsController implements HighlightsResource {
private final StorageService storageService;
private final ObjectMapper objectMapper;
private final AccessControlService accessControlService;
private final FileStatusService fileStatusService;
private final ReanalysisService reanalysisService;
@SneakyThrows
@PreAuthorize("hasAuthority('" + GET_HIGHLIGHTS + "')")
public Highlights getHighlights(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
fileStatusService.getStatus(fileId);
if (storageService.objectExists(getStorageId(dossierId, fileId, FileType.TEXT_HIGHLIGHTS))) {
return objectMapper.readValue(storageService.getObject(getStorageId(dossierId, fileId, FileType.TEXT_HIGHLIGHTS)).getInputStream(), Highlights.class);
}
return new Highlights();
}
@PreAuthorize("hasAuthority('" + CONVERT_HIGHLIGHTS + "')")
public void convertHighlights(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody AnnotationIds annotationIds) {
try {
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
reanalysisService.convertTextHighlights(new TextHighlightConversionRequest(dossierId, fileId, annotationIds.getIds(), TextHighlightConversionOperation.CONVERT));
} catch (FeignException e) {
throw processFeignException(e);
}
}
@PreAuthorize("hasAuthority('" + DELETE_HIGHLIGHTS + "')")
public void deleteHighlights(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody AnnotationIds annotationIds) {
try {
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
reanalysisService.convertTextHighlights(new TextHighlightConversionRequest(dossierId, fileId, annotationIds.getIds(), TextHighlightConversionOperation.REMOVE));
} catch (FeignException e) {
throw processFeignException(e);
}
}
@PreAuthorize("hasAuthority('" + DELETE_IMPORTED_REDACTIONS + "')")
public void deleteImportedRedactions(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody AnnotationIds annotationIds) {
try {
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
reanalysisService.deleteImportedRedactions(new DeleteImportedRedactionsRequest(dossierId, fileId, annotationIds.getIds()));
} catch (FeignException e) {
throw processFeignException(e);
}
}
}

View File

@ -0,0 +1,81 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_LEGAL_BASIS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_LEGAL_BASIS;
import java.util.List;
import javax.transaction.Transactional;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.LegalBasisMappingPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.LegalBasisMappingResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.legalbasis.LegalBasis;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class LegalBasisMappingController implements LegalBasisMappingResource {
private final LegalBasisMappingPersistenceService legalBasisMappingPersistenceService;
private final AuditPersistenceService auditPersistenceService;
@Override
@PreAuthorize("hasAuthority('" + WRITE_LEGAL_BASIS + "')")
public void deleteLegalBasis(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody List<String> legalBasisNames) {
legalBasisMappingPersistenceService.deleteLegalBasis(dossierTemplateId, legalBasisNames);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Legal basis mapping has been changed.")
.build());
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_LEGAL_BASIS + "')")
public void addOrUpdateLegalBasis(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody LegalBasis legalBasis) {
legalBasisMappingPersistenceService.addOrUpdateLegalBasis(dossierTemplateId, legalBasis);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Legal basis mapping has been changed.")
.build());
}
@PreAuthorize("hasAuthority('" + WRITE_LEGAL_BASIS + "')")
public void setLegalBasisMapping(@RequestBody List<LegalBasis> legalBasisMapping, @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
legalBasisMappingPersistenceService.setLegalBasisMapping(dossierTemplateId, legalBasisMapping);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierTemplateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Legal basis mapping has been changed.")
.build());
}
@Transactional
@PreAuthorize("hasAuthority('" + READ_LEGAL_BASIS + "')")
public List<LegalBasis> getLegalBasisMapping(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
return MagicConverter.convert(legalBasisMappingPersistenceService.getLegalBasisMapping(dossierTemplateId), LegalBasis.class);
}
}

View File

@ -0,0 +1,133 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_LICENSE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.UPDATE_LICENSE;
import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.service.v1.api.external.resource.LicenseResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.Feature;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.FeatureType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.License;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.RedactionLicenseModel;
import com.iqser.red.storage.commons.service.StorageService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class LicenseController implements LicenseResource {
private static final String LICENSE_OBJECT_ID = "license/redact-manager-license.json";
private static final String DEMO_PDFTRON_LICENSE = "demo:1650351709282:7bd235e003000000004ec28a6743e1163a085e2115de2536ab6e2cfe5a";
private static final String LICENSE_CUSTOMER = "LICENSE_CUSTOMER";
private static final String LICENSE_START = "LICENSE_START";
private static final String LICENSE_END = "LICENSE_END";
private static final String LICENSE_PAGE_COUNT = "LICENSE_PAGE_COUNT";
private static final String PDFTRON_FRONTEND_LICENSE = "PDFTRON_FRONTEND_LICENSE";
private static final String PDFTRON_FEATURE = "pdftron";
private static final String PROCESSING_PAGES_FEATURE = "processingPages";
private static final String DEFAULT_PROCESSING_PAGES = "200000";
private final StorageService storageService;
private final Environment environment;
@Override
@SneakyThrows
@PreAuthorize("hasAuthority('" + UPDATE_LICENSE + "')")
public void updateLicense(RedactionLicenseModel licenseModel) {
storageService.storeJSONObject(LICENSE_OBJECT_ID, licenseModel);
}
@Override
@PreAuthorize("hasAuthority('" + READ_LICENSE + "')")
public RedactionLicenseModel getLicense() {
if (storageService.objectExists(LICENSE_OBJECT_ID)) {
try {
return storageService.readJSONObject(LICENSE_OBJECT_ID, RedactionLicenseModel.class);
} catch (Exception e) {
return generateDemoLicense();
}
} else {
return generateDemoLicense();
}
}
private RedactionLicenseModel generateDemoLicense() {
License demo = new License();
demo.setId("RedactManager");
demo.setName("RedactManager License");
demo.setProduct("RedactManager");
var licensedTo = environment.getProperty(LICENSE_CUSTOMER, "RedactManager Demo License");
demo.setLicensedTo(licensedTo);
var licenseStartDate = parseDate(environment.getProperty(LICENSE_START));
var start = licenseStartDate != null ? licenseStartDate : OffsetDateTime.now().withDayOfYear(1).withHour(0).withMinute(0).withSecond(0).truncatedTo(ChronoUnit.SECONDS);
demo.setValidFrom(start);
var licenseEndDate = parseDate(environment.getProperty(LICENSE_END));
var end = licenseEndDate != null ? licenseEndDate : OffsetDateTime.now()
.withMonth(12)
.withDayOfMonth(31)
.withHour(0)
.withMinute(0)
.withSecond(0)
.truncatedTo(ChronoUnit.SECONDS);
demo.setValidUntil(end);
var pdftronLicense = environment.getProperty(PDFTRON_FRONTEND_LICENSE, DEMO_PDFTRON_LICENSE);
demo.getFeatures().add(Feature.builder().name(PDFTRON_FEATURE).type(FeatureType.STRING).value(pdftronLicense).build());
var pageCount = Long.parseLong(environment.getProperty(LICENSE_PAGE_COUNT, DEFAULT_PROCESSING_PAGES));
demo.getFeatures().add(Feature.builder().name(PROCESSING_PAGES_FEATURE).type(FeatureType.NUMBER).value(pageCount).build());
var demoLicense = new RedactionLicenseModel();
demoLicense.setActiveLicense("RedactManager");
demoLicense.getLicenses().add(demo);
return demoLicense;
}
private OffsetDateTime parseDate(String date) {
if (StringUtils.isEmpty(date)) {
return null;
}
var parts = date.split("-");
if (parts.length != 3) {
return null;
}
try {
return OffsetDateTime.now()
.withYear(Integer.parseInt(parts[2]))
.withMonth(Integer.parseInt(parts[1]))
.withDayOfMonth(Integer.parseInt(parts[0]))
.withHour(0)
.withMinute(0)
.withSecond(0)
.truncatedTo(ChronoUnit.SECONDS);
} catch (Exception e) {
log.debug("Failed to parse date: {}", date);
return null;
}
}
}

View File

@ -0,0 +1,47 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_LICENSE_REPORT;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.service.LicenseReportService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.LicenseReportResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.LicenseReport;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.LicenseReportRequest;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class LicenseReportController implements LicenseReportResource {
private static final String LICENSE_AUDIT_KEY = "License";
private final AuditPersistenceService auditPersistenceService;
private final LicenseReportService licenseReportService;
@Override
@PreAuthorize("hasAuthority('" + READ_LICENSE_REPORT + "')")
public LicenseReport getReport(@RequestBody LicenseReportRequest reportRequest,
@RequestParam(value = "offset", defaultValue = "0") int offset,
@RequestParam(value = "limit", defaultValue = "20") int limit) {
LicenseReport licenseReport = licenseReportService.getLicenseReport(reportRequest, offset, limit);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(LICENSE_AUDIT_KEY)
.category(AuditCategory.LICENSE.name())
.message("License report has been viewed.")
.build());
return licenseReport;
}
}

View File

@ -0,0 +1,77 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_NOTIFICATIONS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.UPDATE_NOTIFICATIONS;
import java.time.OffsetDateTime;
import java.util.List;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.NotificationResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.NotificationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.notification.Notification;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class NotificationController implements NotificationResource {
private final NotificationPersistenceService notificationPersistenceService;
@Override
@PreAuthorize("hasAuthority('" + READ_NOTIFICATIONS + "')")
public JSONPrimitive<Boolean> hasNewNotificationsSince(@RequestBody JSONPrimitive<OffsetDateTime> since) {
return JSONPrimitive.of(notificationPersistenceService.hasNewNotificationsSince(KeycloakSecurity.getUserId(), since.getValue()));
}
@PreAuthorize("hasAuthority('" + UPDATE_NOTIFICATIONS + "')")
public void toggleNotificationSeen(@RequestBody List<String> notificationIds, @RequestParam(SET_SEEN_PARAM) boolean setSeen) {
notificationIds.stream().map(Long::valueOf).forEach(notificationId -> {
if (setSeen) {
notificationPersistenceService.setSeenDate(KeycloakSecurity.getUserId(), notificationId, OffsetDateTime.now());
} else {
notificationPersistenceService.setSeenDate(KeycloakSecurity.getUserId(), notificationId, null);
}
});
}
@PreAuthorize("hasAuthority('" + UPDATE_NOTIFICATIONS + "')")
public void toggleNotificationRead(@RequestBody List<String> notificationIds, @RequestParam(SET_READ_PARAM) boolean setRead) {
notificationIds.stream().map(Long::valueOf).forEach(notificationId -> {
if (setRead) {
notificationPersistenceService.setReadDate(KeycloakSecurity.getUserId(), notificationId, OffsetDateTime.now());
} else {
notificationPersistenceService.setReadDate(KeycloakSecurity.getUserId(), notificationId, null);
}
});
}
@PreAuthorize("hasAuthority('" + UPDATE_NOTIFICATIONS + "')")
public void delete(@RequestBody List<String> notificationIds) {
notificationIds.stream().map(Long::valueOf).forEach(notificationId -> {
notificationPersistenceService.softDelete(KeycloakSecurity.getUserId(), notificationId);
});
}
@PreAuthorize("hasAuthority('" + READ_NOTIFICATIONS + "')")
public NotificationResponse getNotifications(@RequestParam(INCLUDE_SEEN_PARAM) boolean includeSeen) {
var notifications = MagicConverter.convert(notificationPersistenceService.getNotifications(KeycloakSecurity.getUserId(), includeSeen), Notification.class);
return NotificationResponse.builder().notifications(notifications).build();
}
}

View File

@ -0,0 +1,49 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_NOTIFICATIONS;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.UPDATE_NOTIFICATIONS;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPreferencesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.external.resource.NotificationPreferencesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.notification.NotificationPreferences;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class NotificationPreferencesController implements NotificationPreferencesResource {
private final NotificationPreferencesPersistenceService notificationPreferencesPersistenceService;
@Override
@PreAuthorize("hasAuthority('" + READ_NOTIFICATIONS + "')")
public NotificationPreferences getNotificationPreferences() {
return notificationPreferencesPersistenceService.getOrCreateNotificationPreferences(KeycloakSecurity.getUserId());
}
@Override
@PreAuthorize("hasAuthority('" + UPDATE_NOTIFICATIONS + "')")
public void setNotificationPreferences(@RequestBody NotificationPreferences notificationPreferences) {
notificationPreferencesPersistenceService.setNotificationPreference(KeycloakSecurity.getUserId(), notificationPreferences);
}
@Override
@PreAuthorize("hasAuthority('" + UPDATE_NOTIFICATIONS + "')")
public void deleteNotificationPreferences() {
notificationPreferencesPersistenceService.deleteNotificationPreferences(KeycloakSecurity.getUserId());
}
}

View File

@ -0,0 +1,138 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.GET_RSS;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionreportservice.RssReportClient;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentOverrideService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RSSResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.ComponentsOverrides;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.rss.RSSFileResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.rss.RSSResponse;
import com.iqser.red.service.redaction.report.v1.api.model.rss.DetailedRSSResponse;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class RSSController implements RSSResource {
private final RssReportClient rssReportClient;
private final ComponentOverrideService componentOverrideService;
private final AuditPersistenceService auditPersistenceService;
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public RSSResponse getRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId) {
return convert(rssReportClient.getRSS(dossierId, fileId));
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
private RSSResponse convert(com.iqser.red.service.redaction.report.v1.api.model.rss.RSSResponse rssResponse) {
return new RSSResponse(rssResponse.getFiles().stream().map(this::convert).collect(Collectors.toList()));
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
private RSSFileResponse convert(com.iqser.red.service.redaction.report.v1.api.model.rss.RSSFileResponse rssFileResponse) {
return new RSSFileResponse(rssFileResponse.getFilename(), rssFileResponse.getResult());
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public DetailedRSSResponse getDetailedRSS(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = "fileId", required = false) String fileId) {
return rssReportClient.getDetailedRSS(dossierId, fileId);
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void addOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody ComponentsOverrides componentsOverrides) {
var rssReport = rssReportClient.getDetailedRSS(dossierId, fileId);
var components = rssReport.getFiles().get(0).getResult();
componentOverrideService.addOverrides(dossierId, fileId, componentsOverrides);
componentsOverrides.getComponentOverrides()
.forEach((key, value) -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component is overwritten with value")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
"ComponentName",
key,
"Action",
"MODIFY",
"OriginalValue",
components.get(key).getOriginalValue(),
"OldValue",
components.get(key).getValue() != null ? components.get(key).getValue() : components.get(key).getOriginalValue(),
"NewValue",
value))
.build()));
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public ComponentsOverrides getOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
return componentOverrideService.getOverrides(dossierId, fileId);
}
@PreAuthorize("hasAuthority('" + GET_RSS + "')")
public void revertOverrides(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody RevertOverrideRequest revertOverrideRequest) {
var rssReport = rssReportClient.getDetailedRSS(dossierId, fileId);
var components = rssReport.getFiles().get(0).getResult();
componentOverrideService.revertOverrides(dossierId, fileId, revertOverrideRequest);
revertOverrideRequest.getComponents()
.forEach(component -> auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("The component override for was reverted")
.details(Map.of(DOSSIER_ID,
dossierId,
FILE_ID,
fileId,
"ComponentName",
component,
"Action",
"REVERT",
"OriginalValue",
components.get(component).getOriginalValue(),
"OldValue",
components.get(component).getValue() != null ? components.get(component).getValue() : components.get(component).getOriginalValue(),
"NewValue",
components.get(component).getOriginalValue()))
.build()));
}
}

View File

@ -0,0 +1,312 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Sets;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.ReanalysisResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.PageExclusionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;
import feign.FeignException;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class ReanalysisController implements ReanalysisResource {
private static final String DOSSIER_ID = "dossierId";
private final ReanalysisService reanalysisService;
private final FileStatusManagementService fileStatusManagementService;
private final AuditPersistenceService auditPersistenceService;
private final AccessControlService accessControlService;
@PreAuthorize("hasAuthority('" + REANALYZE_DOSSIER + "')")
public void reanalyzeDossier(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
} catch (AccessDeniedException e) {
throw new NotFoundException("Object not found");
}
accessControlService.verifyUserHasAccessPermissions(dossierId);
reanalysisService.reanalyzeDossier(dossierId, force);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("Reanalyse dossier was triggered")
.build());
}
@PreAuthorize("hasAuthority('" + REANALYZE_FILE + "')")
public void reanalyzeFile(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {
reanalysisService.reanalyzeFiles(dossierId, Sets.newHashSet(fileId), force);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("Reanalyse file was triggered")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
}
@Override
@PreAuthorize("hasAuthority('" + REANALYZE_FILE + "')")
public void reanalyzeFilesForDossier(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody List<String> fileIds,
@RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {
reanalysisService.reanalyzeFiles(dossierId, new HashSet<>(fileIds), force);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("Reanalyse files was triggered")
.details(Map.of(DOSSIER_ID, dossierId, "number", fileIds.size()))
.build());
}
@Override
@PreAuthorize("hasAuthority('" + REANALYZE_DOSSIER + "')")
public void ocrDossier(@PathVariable(DOSSIER_ID) String dossierId) {
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
} catch (AccessDeniedException e) {
throw new NotFoundException("Object not found");
}
accessControlService.verifyUserHasAccessPermissions(dossierId);
reanalysisService.ocrDossier(dossierId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOSSIER.name())
.message("OCR and reanalyse dossier was triggered")
.build());
}
@Override
@PreAuthorize("hasAuthority('" + REANALYZE_FILE + "')")
public void ocrFile(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {
validateOCR(dossierId, fileId);
reanalysisService.ocrFile(dossierId, fileId, force);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("OCR and reanalyse file was triggered")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
}
@Override
@PreAuthorize("hasAuthority('" + REANALYZE_FILE + "')")
public void ocrFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody Set<String> fileIds) {
fileIds.forEach(fileId -> validateOCR(dossierId, fileId));
reanalysisService.ocrFiles(dossierId, fileIds);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(dossierId)
.category(AuditCategory.DOCUMENT.name())
.message("OCR and reanalyse was triggered")
.details(Map.of(DOSSIER_ID, dossierId, "number", fileIds.size()))
.build());
}
@PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_FILE + "')")
public void toggleAutomaticAnalysis(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(EXCLUDED_STATUS_PARAM) boolean excludedFromAutomaticAnalysis) {
accessControlService.verifyUserIsReviewer(dossierId, fileId);
fileStatusManagementService.toggleAutomaticAnalysis(dossierId, fileId, excludedFromAutomaticAnalysis);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Toggle Exclusion status: File excluded from automatic analysis: " + excludedFromAutomaticAnalysis)
.build());
}
@Override
@PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_FILE + "')")
public void toggleExclusion(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(name = EXCLUDED_STATUS_PARAM, required = false, defaultValue = "false") boolean excluded) {
var status = fileStatusManagementService.getFileStatus(fileId);
if (!(status.getAssignee() == null && status.isExcluded())) { // Needed to include documents after 3.0 migration.
accessControlService.verifyUserIsReviewer(dossierId, fileId);
}
fileStatusManagementService.toggleExclusion(dossierId, fileId, excluded);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Toggle Exclusion status: File excluded from analysis: " + excluded)
.build());
}
@PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_FILE + "')")
public void toggleAutomaticAnalysisForList(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody Set<String> fileIds,
@RequestParam(EXCLUDED_STATUS_PARAM) boolean excludedFromAutomaticAnalysis) {
List<String> errorIds = new ArrayList<>();
for (var fileId : fileIds) {
try {
this.toggleAutomaticAnalysis(dossierId, fileId, excludedFromAutomaticAnalysis);
} catch (FeignException e) {
errorIds.add(fileId);
}
}
if (!errorIds.isEmpty()) {
throw new BadRequestException("Failed to delete files with ids: " + errorIds);
}
}
@PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_FILE + "')")
public void toggleExclusionForList(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody Set<String> fileIds,
@RequestParam(name = EXCLUDED_STATUS_PARAM, required = false, defaultValue = "false") boolean excluded) {
List<String> errorIds = new ArrayList<>();
for (var fileId : fileIds) {
try {
this.toggleExclusion(dossierId, fileId, excluded);
} catch (FeignException e) {
errorIds.add(fileId);
}
}
if (!errorIds.isEmpty()) {
throw new BadRequestException("Failed to delete files with ids: " + errorIds);
}
}
@Override
@PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_PAGES + "')")
public void excludePages(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody PageExclusionRequest pageExclusionRequest) {
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
Set<Integer> excludedPages = new HashSet<>();
for (var pageRange : pageExclusionRequest.getPageRanges()) {
for (int i = pageRange.getStartPage(); i <= pageRange.getEndPage(); i++) {
excludedPages.add(i);
}
}
fileStatusManagementService.excludePages(dossierId, fileId, excludedPages);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Page exclusions added for file")
.build());
}
@Override
@PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_PAGES + "')")
public void includePages(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody PageExclusionRequest pageInclusionRequest) {
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
Set<Integer> includePages = new HashSet<>();
for (var pageRange : pageInclusionRequest.getPageRanges()) {
for (int i = pageRange.getStartPage(); i <= pageRange.getEndPage(); i++) {
includePages.add(i);
}
}
fileStatusManagementService.includePages(dossierId, fileId, includePages);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Page inclusions added for file")
.build());
}
@PreAuthorize("hasAuthority('" + REINDEX + "')")
public void reindex(@RequestParam(value = "dossierId", required = false) String dossierId,
@RequestParam(value = "dropIndex", required = false, defaultValue = FALSE) boolean dropIndex,
@RequestBody List<String> fileIds) {
reanalysisService.reindex(dossierId, dropIndex, new HashSet<>(fileIds));
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId("redaction")
.category(AuditCategory.INDEX.name())
.message("Reindexing has been triggered" + (dropIndex ? " (with drop index)." : "."))
.build());
}
private void validateOCR(String dossierId, String fileId) {
var status = fileStatusManagementService.getFileStatus(fileId);
if (status.getWorkflowStatus() == WorkflowStatus.APPROVED) {
throw new BadRequestException("Cannot OCR approved file");
}
if (status.getAssignee() == null) {
accessControlService.verifyUserIsMemberOrApprover(dossierId);
} else {
accessControlService.verifyUserIsReviewer(dossierId, fileId);
}
}
}

View File

@ -0,0 +1,173 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_REDACTION_LOG;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.RedactionLogService;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RedactionLogResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.FilteredRedactionLogRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.RedactionLog;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.section.SectionGrid;
import com.iqser.red.storage.commons.exception.StorageObjectDoesNotExist;
import com.iqser.red.storage.commons.service.StorageService;
import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
@RestController
@RequiredArgsConstructor
public class RedactionLogController implements RedactionLogResource {
private final RedactionLogService redactionLogService;
private final StorageService storageService;
private final FileStatusService fileStatusService;
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public RedactionLog getRedactionLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "excludedType", required = false) List<String> excludedTypes,
@RequestParam(value = "withManualRedactions", required = false, defaultValue = "true") boolean withManualRedactions,
@RequestParam(value = "includeFalsePositives", required = false, defaultValue = "false") boolean includeFalsePositives) {
try {
return redactionLogService.getRedactionLog(dossierId, fileId, excludedTypes, withManualRedactions, includeFalsePositives);
} catch (FeignException e) {
throw processFeignException(e);
}
}
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public SectionGrid getSectionGrid(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
try {
return redactionLogService.getSectionGrid(dossierId, fileId);
} catch (FeignException e) {
throw processFeignException(e);
}
}
@SneakyThrows
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public ResponseEntity<?> getSectionText(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
try {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.parseMediaType("application/zip"));
var fileStatus = fileStatusService.getStatus(fileId);
String filename = fileStatus.getFilename();
if (filename != null) {
var index = filename.lastIndexOf(".");
String prefix = filename.substring(0, index);
filename = prefix + ".json";
httpHeaders.add("Content-Disposition", "attachment; filename=" + prefix + ".zip");
}
byte[] zipBytes = getZippedBytes(dossierId, fileId, filename, FileType.TEXT);
return new ResponseEntity<>(zipBytes, httpHeaders, HttpStatus.OK);
} catch (FeignException e) {
throw processFeignException(e);
}
}
@SneakyThrows
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public ResponseEntity<?> getSimplifiedSectionText(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
try {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.parseMediaType("application/zip"));
var fileStatus = fileStatusService.getStatus(fileId);
String filename = fileStatus.getFilename();
if (filename != null) {
var index = filename.lastIndexOf(".");
String prefix = filename.substring(0, index);
filename = prefix + ".json";
httpHeaders.add("Content-Disposition", "attachment; filename*=utf-8''" + StringEncodingUtils.urlEncode(prefix) + ".zip");
}
byte[] zipBytes = getZippedBytes(dossierId, fileId, filename, FileType.SIMPLIFIED_TEXT);
return new ResponseEntity<>(zipBytes, httpHeaders, HttpStatus.OK);
} catch (StorageObjectDoesNotExist e) {
throw new RuntimeException("Simplified Text is not available", e);
} catch (FeignException e) {
throw processFeignException(e);
}
}
@PreAuthorize("hasAuthority('" + READ_REDACTION_LOG + "')")
public RedactionLog getFilteredRedactionLog(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestBody FilteredRedactionLogRequest filteredRedactionLogRequest) {
try {
return redactionLogService.getFilteredRedactionLog(dossierId, fileId, filteredRedactionLogRequest);
} catch (FeignException e) {
throw processFeignException(e);
}
}
private byte[] getZippedBytes(String dossierId, String fileId, String filename, FileType fileType) throws IOException {
try {
String objectId = dossierId + "/" + fileId + "." + fileType.name() + fileType.getExtension();
var inputStreamResource = storageService.getObject(objectId);
try (var inputStream = inputStreamResource.getInputStream()) {
return zipBytes(filename, inputStream.readAllBytes());
}
} catch (StorageObjectDoesNotExist e) {
throw new RuntimeException(String.format("%s is not available", fileType.name()), e);
}
}
public static byte[] zipBytes(String filename, byte[] input) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos);
ZipEntry entry = new ZipEntry(filename);
entry.setSize(input.length);
zos.putNextEntry(entry);
zos.write(input);
zos.closeEntry();
zos.close();
return baos.toByteArray();
}
}

View File

@ -0,0 +1,208 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DELETE_REPORT_TEMPLATE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.DOWNLOAD_REPORT_TEMPLATE;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.GET_REPORT_TEMPLATES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.UPLOAD_REPORT_TEMPLATE;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionreportservice.PlaceholderClient;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionreportservice.ReportTemplatePlaceholderClient;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.ReportTemplateService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileAttributeConfigPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ReportTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.service.v1.api.external.resource.ReportTemplateResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.PlaceholdersResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ReportTemplateUpdateRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.ReportTemplate;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.ReportTemplateUploadRequest;
import com.iqser.red.storage.commons.exception.StorageObjectDoesNotExist;
import com.iqser.red.storage.commons.service.StorageService;
import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class ReportTemplateController implements ReportTemplateResource {
private final ReportTemplatePlaceholderClient reportTemplatePlaceholderClient;
private final PlaceholderClient placeholderClient;
private final AuditPersistenceService auditPersistenceService;
private final DossierAttributeConfigPersistenceService dossierAttributeConfigPersistenceService;
private final FileAttributeConfigPersistenceService fileAttributeConfigPersistenceService;
private final StorageService storageService;
private final ReportTemplatePersistenceService reportTemplatePersistenceService;
private final ReportTemplateService reportTemplateService;
@Override
@PreAuthorize("hasAuthority('" + GET_REPORT_TEMPLATES + "')")
public List<ReportTemplate> getReportTemplatesByPlaceholder(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody JSONPrimitive<String> placeholder) {
try {
// TODO MIGRATION ***
// return reportTemplatePlaceholderClient.getReportTemplatesByPlaceholder(dossierTemplateId, placeholder);
return null;
} catch (FeignException e) {
throw processFeignException(e);
}
}
@Override
@PreAuthorize("hasAuthority('" + UPLOAD_REPORT_TEMPLATE + "')")
public ReportTemplate uploadTemplate(@RequestPart(name = "file") MultipartFile file,
@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId,
@RequestParam(value = MULTI_FILE_REPORT, required = false, defaultValue = "false") boolean multiFileReport,
@RequestParam(value = ACTIVE_BY_DEFAULT, required = false, defaultValue = "false") boolean activeByDefault) {
try {
if (file.getOriginalFilename() != null) {
ReportTemplateUploadRequest reportTemplateUploadRequest = ReportTemplateUploadRequest.builder()
.template(file.getBytes())
.fileName(file.getOriginalFilename())
.dossierTemplateId(dossierTemplateId)
.activeByDefault(activeByDefault)
.multiFileReport(multiFileReport)
.build();
var reportTemplate = reportTemplateService.uploadTemplate(reportTemplateUploadRequest);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(reportTemplate.getTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Report template was uploaded.")
.details(Map.of("DossierTemplateId", dossierTemplateId))
.build());
return reportTemplate;
} else {
throw new BadRequestException("Could not upload file, no filename provided.");
}
} catch (IOException e) {
throw new BadRequestException(e.getMessage(), e);
}
}
@Override
@PreAuthorize("hasAuthority('" + GET_REPORT_TEMPLATES + "')")
public List<ReportTemplate> getAvailableReportTemplates(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {
return MagicConverter.convert(reportTemplatePersistenceService.findByDossierTemplateId(dossierTemplateId), ReportTemplate.class);
}
@Override
@PreAuthorize("hasAuthority('" + DOWNLOAD_REPORT_TEMPLATE + "')")
public ResponseEntity<?> downloadReportTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @PathVariable(TEMPLATE_ID) String templateId) {
try {
var reportTemplate = reportTemplatePersistenceService.find(templateId);
byte[] file = IOUtils.toByteArray(storageService.getObject(reportTemplate.getStorageId()).getInputStream());
return getResponseEntity(file, reportTemplate.getFileName(), MediaType.APPLICATION_OCTET_STREAM);
} catch (StorageObjectDoesNotExist | IOException e) {
throw new NotFoundException("Template does not exist");
}
}
private ResponseEntity<?> getResponseEntity(byte[] file, String filename, MediaType mediaType) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(mediaType);
if (filename != null) {
httpHeaders.add("Content-Disposition", "attachment; filename*=utf-8''" + StringEncodingUtils.urlEncode(filename));
}
InputStream is = new ByteArrayInputStream(file);
return new ResponseEntity<>(new InputStreamResource(is), httpHeaders, HttpStatus.OK);
}
@Override
@PreAuthorize("hasAuthority('" + DELETE_REPORT_TEMPLATE + "')")
public void deleteTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @PathVariable(TEMPLATE_ID) String templateId) {
var storageId = reportTemplatePersistenceService.find(templateId).getStorageId();
storageService.deleteObject(storageId);
reportTemplatePersistenceService.delete(templateId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(templateId)
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Report template was deleted.")
.details(Map.of("DossierTemplateId", dossierTemplateId))
.build());
}
@Override
public PlaceholdersResponse getAvailablePlaceholders(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {
PlaceholdersResponse availablePlaceholders = new PlaceholdersResponse();
List<String> dossierPlaceholders = new ArrayList<>();
for (var dossierAttributeConfig : dossierAttributeConfigPersistenceService.getDossierAttributes(dossierTemplateId)) {
dossierPlaceholders.add(dossierAttributeConfig.getPlaceholder());
}
availablePlaceholders.setDossierAttributePlaceholders(dossierPlaceholders);
List<String> filePlaceholders = new ArrayList<>();
for (var fileAttributeConfig : fileAttributeConfigPersistenceService.getFileAttributes(dossierTemplateId)) {
filePlaceholders.add(fileAttributeConfig.getPlaceholder());
}
availablePlaceholders.setFileAttributePlaceholders(filePlaceholders);
List<String> generalPlaceholders = placeholderClient.getPlaceholders();
availablePlaceholders.setGeneralPlaceholders(generalPlaceholders);
return availablePlaceholders;
}
@PreAuthorize("hasAuthority('" + UPLOAD_REPORT_TEMPLATE + "')")
public void updateTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId,
@PathVariable(TEMPLATE_ID) String templateId,
@RequestBody ReportTemplateUpdateRequest updateRequest) {
reportTemplatePersistenceService.updateTemplate(dossierTemplateId, templateId, updateRequest);
}
}

View File

@ -0,0 +1,111 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_RULES;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_RULES;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionservice.RedactionClient;
import com.iqser.red.service.persistence.management.v1.processor.exception.FileUploadException;
import com.iqser.red.service.persistence.management.v1.processor.exception.InvalidRulesException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RulesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.Rules;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class RulesController implements RulesResource {
private static final String DOWNLOAD_FILE_NAME = "rules.drl";
private final RulesPersistenceService rulesPersistenceService;
private final RedactionClient redactionServiceClient;
private final AuditPersistenceService auditPersistenceService;
@Override
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public void upload(@RequestBody Rules rules) {
try {
redactionServiceClient.testRules(rules.getRules());
} catch (FeignException e) {
if (e.status() == HttpStatus.BAD_REQUEST.value()) {
throw new InvalidRulesException("Rules could not be updated, validation check failed: " + e.getMessage());
}
throw processFeignException(e);
}
rulesPersistenceService.setRules(rules.getRules(), rules.getDossierTemplateId());
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(rules.getDossierTemplateId())
.category(AuditCategory.DOSSIER_TEMPLATE.name())
.message("Rules have been updated")
.build());
}
@Override
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public Rules download(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
return new Rules(rulesPersistenceService.getRules(dossierTemplateId).getValue(), dossierTemplateId);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
public void uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestPart(name = "file") MultipartFile file) {
try {
upload(new Rules(new String(file.getBytes(), StandardCharsets.UTF_8), dossierTemplateId));
} catch (IOException e) {
throw new FileUploadException("Could not upload file.", e);
}
}
@Override
@PreAuthorize("hasAuthority('" + READ_RULES + "')")
public ResponseEntity<?> downloadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {
byte[] data = download(dossierTemplateId).getRules().getBytes(StandardCharsets.UTF_8);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
httpHeaders.add("Content-Disposition", "attachment; filename*=utf-8\"" + DOWNLOAD_FILE_NAME + "\"");
InputStream is = new ByteArrayInputStream(data);
return new ResponseEntity<>(new InputStreamResource(is), httpHeaders, HttpStatus.OK);
}
}

View File

@ -0,0 +1,99 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_SMTP_CONFIGURATION;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.WRITE_SMTP_CONFIGURATION;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.keycloak.commons.RealmService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.SMTPConfigurationService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.SMTPConfigurationResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.SMTPConfiguration;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class SMTPConfigurationController implements SMTPConfigurationResource {
private final RealmService realmService;
private final SMTPConfigurationService smtpConfigurationService;
private final ObjectMapper objectMapper;
@Override
@PreAuthorize("hasAuthority('" + READ_SMTP_CONFIGURATION + "')")
public SMTPConfiguration getCurrentSMTPConfiguration() {
return smtpConfigurationService.getCurrentSMTPConfiguration(true);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_SMTP_CONFIGURATION + "')")
public void updateSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel) {
smtpConfigurationService.updateSMTPConfiguration(smtpConfigurationModel);
// also update in KC
var realmRepresentation = realmService.realm().toRepresentation();
var propertiesMap = convertSMTPConfigurationModelToMap(smtpConfigurationModel);
realmRepresentation.setSmtpServer(propertiesMap);
realmService.realm().update(realmRepresentation);
}
private Map<String, String> convertSMTPConfigurationModelToMap(SMTPConfiguration smtpConfigurationModel) {
Map<String, Object> propertiesMap = objectMapper.convertValue(smtpConfigurationModel, Map.class);
Map<String, String> stringPropertiesMap = new HashMap<>();
propertiesMap.forEach((key, value) -> {
if (value != null) {
stringPropertiesMap.put(key, value.toString());
} else {
stringPropertiesMap.put(key, "");
}
});
return stringPropertiesMap;
}
@SneakyThrows
@Override
@PreAuthorize("hasAuthority('" + WRITE_SMTP_CONFIGURATION + "')")
public void testSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel) {
var currentUserEmail = realmService.realm().users().get(KeycloakSecurity.getUserId()).toRepresentation().getEmail();
smtpConfigurationService.testSMTPConfiguration(currentUserEmail, smtpConfigurationModel);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_SMTP_CONFIGURATION + "')")
public void clearSMTPConfiguration() {
smtpConfigurationService.deleteConfiguration();
// also update in KC
var realmRepresentation = realmService.realm().toRepresentation();
realmRepresentation.setSmtpServer(new HashMap<>());
realmService.realm().update(realmRepresentation);
}
}

View File

@ -0,0 +1,38 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.SEARCH;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.client.searchservice.SearchClient;
import com.iqser.red.service.persistence.service.v1.api.external.resource.SearchResource;
import com.iqser.red.service.search.v1.model.SearchRequest;
import com.iqser.red.service.search.v1.model.SearchResult;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class SearchController implements SearchResource {
private final SearchClient searchClient;
@PreAuthorize("hasAuthority('" + SEARCH + "')")
public SearchResult search(@RequestBody SearchRequest searchRequest) {
return searchClient.getDossierStatus(searchRequest);
}
@Deprecated
@PreAuthorize("hasAuthority('" + SEARCH + "')")
public SearchResult searchDeprecated(@RequestBody SearchRequest searchRequest) {
searchRequest.setPage(searchRequest.getPage() - 1);
return searchClient.getDossierStatus(searchRequest);
}
}

View File

@ -0,0 +1,486 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.*;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.keycloak.commons.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.UserService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.NotificationPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.StatusResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileAttributes;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AddNotificationRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.ProcessingStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.notification.NotificationType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class StatusController implements StatusResource {
private static final Set<String> VALID_MEMBER_ROLES = Set.of(ApplicationRoles.RED_USER_ROLE, ApplicationRoles.RED_MANAGER_ROLE);
private static final String DOSSIER_ID = "dossierId";
private static final String FILE_ID = "fileId";
private static final String FILE_NAME = "fileName";
private final FileStatusManagementService fileStatusManagementService;
private final UserService userService;
private final DossierManagementService dossierManagementService;
private final AuditPersistenceService auditPersistenceService;
private final AccessControlService accessControlService;
private final NotificationPersistenceService notificationPersistenceService;
private final DossierACLService dossierACLService;
@Override
@PreAuthorize("hasAuthority('" + READ_FILE_STATUS + "')")
public JSONPrimitive<Boolean> hasChangesSince(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody JSONPrimitive<OffsetDateTime> since) {
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
} catch (AccessDeniedException e) {
return JSONPrimitive.of(false);
}
return fileStatusManagementService.hasChangesSince(dossierId, since);
}
@Override
@PreAuthorize("hasAuthority('" + READ_FILE_STATUS + "')")
public Map<String, List<FileStatus>> getDossierStatus(@RequestBody List<String> dossierIds) {
Map<String, List<FileStatus>> response = new HashMap<>();
for (String dossierId : dossierIds) {
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
List<FileStatus> statusList = fileStatusManagementService.getDossierStatus(dossierId)
.stream()
.filter(fileStatus -> !fileStatus.isSoftOrHardDeleted())
.map(this::convert)
.collect(Collectors.toList());
response.put(dossierId, statusList);
} catch (AccessDeniedException e) {
continue;
}
}
return response;
}
@Override
@PreAuthorize("hasAuthority('" + READ_FILE_STATUS + "')")
@Transactional
public Map<String, List<FileStatus>> getSoftDeletedFilesForDossiers(@RequestBody List<String> dossierIds) {
List<FileStatus> statusList;
List<String> dossiersWithViewPermissions = new ArrayList<>();
for (var dossierId : dossierIds) {
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
dossiersWithViewPermissions.add(dossierId);
} catch (AccessDeniedException e) {
continue;
}
}
if (dossiersWithViewPermissions.isEmpty()) {
return new HashMap<>();
}
statusList = fileStatusManagementService.getSoftDeletedForDossierList(dossiersWithViewPermissions).stream().map(this::convert).collect(Collectors.toList());
return statusList.stream().collect(Collectors.groupingBy(FileStatus::getDossierId, Collectors.toList()));
}
@PreAuthorize("hasAuthority('" + READ_FILE_STATUS + "')")
public List<FileStatus> getDossierStatus(@PathVariable(DOSSIER_ID) String dossierId) {
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
return fileStatusManagementService.getDossierStatus(dossierId)
.stream()
.filter(fileStatus -> !fileStatus.isSoftOrHardDeleted())
.map(this::convert)
.collect(Collectors.toList());
} catch (AccessDeniedException e) {
return new ArrayList<>();
}
}
@PreAuthorize("hasAuthority('" + READ_FILE_STATUS + "')")
public FileStatus getFileStatus(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
return convert(fileStatusManagementService.getFileStatus(fileId));
}
@Override
@PreAuthorize("hasAuthority('" + SET_REVIEWER + "')")
public void setCurrentFileAssignee(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(name = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId) {
accessControlService.verifyUserIsMemberOrApprover(dossierId);
log.debug("Requested [setFileReviewer] for dossier: {} / file: {} / reviewer: {}", dossierId, fileId, assigneeId);
if (assigneeId != null) {
var user = userService.getUserById(assigneeId);
if (user.isEmpty()) {
userService.removeDeletedUsers(Collections.singleton(assigneeId));
throw new BadRequestException("Unknown user=" + assigneeId);
}
// check he has a manager role, thus he can be the owner
if (user.get().getRoles().stream().noneMatch(VALID_MEMBER_ROLES::contains)) {
throw new BadRequestException("Make sure each provided member id has the permission to be a member of a dossier.");
}
// check assignee is a member
accessControlService.verifyUserIsMemberOrApprover(dossierId, assigneeId);
} else {
// check if file is already unassigned (cannot unassign an unassigned file)
if (fileStatusManagementService.getFileStatus(fileId).getAssignee() == null) {
throw new BadRequestException("File is already unassigned!");
}
}
var fileStatus = fileStatusManagementService.getFileStatus(fileId);
log.debug("Obtained status: {}", fileStatus);
fileStatusManagementService.setCurrentFileAssignee(dossierId, fileId, assigneeId);
if (assigneeId == null) {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Reviewer was unassigned from document")
.details(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
} else {
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Reviewer was assigned to document")
.details(Map.of(DOSSIER_ID, dossierId, "reviewer", assigneeId))
.build());
}
if (assigneeId != null && !assigneeId.equals(KeycloakSecurity.getUserId())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(assigneeId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.ASSIGN_REVIEWER.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
}
if (assigneeId == null || fileStatus.getAssignee() != null && !fileStatus.getAssignee().equals(assigneeId) && !KeycloakSecurity.getUserId()
.equals(fileStatus.getAssignee())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(fileStatus.getAssignee())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.UNASSIGNED_FROM_FILE.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
}
}
@PreAuthorize("hasAuthority('" + SET_REVIEWER + "')")
public void setStatusUnderReview(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId) {
var fileStatus = fileStatusManagementService.getFileStatus(fileId);
setStatusUnderReviewForFile(dossierId, fileId, assigneeId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Document status was changed to Under Review")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
if (assigneeId != null && !assigneeId.equals(KeycloakSecurity.getUserId())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(assigneeId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.ASSIGN_REVIEWER.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
}
generatePossibleUnassignedFromFileNotification(dossierId, fileId, fileStatus, assigneeId);
}
@PreAuthorize("hasAuthority('" + SET_STATUS_UNDER_APPROVAL + "')")
public void setStatusUnderApproval(@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(name = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId) {
var fileStatus = fileStatusManagementService.getFileStatus(fileId);
setStatusUnderApprovalForFile(dossierId, fileId, assigneeId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Document status was changed to Under Approval")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
if (assigneeId != null && !assigneeId.equals(KeycloakSecurity.getUserId())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(assigneeId)
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.ASSIGN_APPROVER.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
}
generatePossibleUnassignedFromFileNotification(dossierId, fileId, fileStatus, assigneeId);
}
@PreAuthorize("hasAuthority('" + SET_STATUS_APPROVED + "')")
public void setStatusApproved(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId) {
accessControlService.verifyUserIsApprover(dossierId);
setStatusApprovedForFile(dossierId, fileId);
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Document status was changed to Approved")
.details(Map.of(DOSSIER_ID, dossierId))
.build());
var dossier = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, false, false));
if (!dossier.getOwnerId().equals(KeycloakSecurity.getUserId())) {
var fileStatus = fileStatusManagementService.getFileStatus(fileId);
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(dossier.getOwnerId())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.DOCUMENT_APPROVED.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, fileStatus.getFilename()))
.build());
}
}
@Override
@PreAuthorize("hasAuthority('" + SET_REVIEWER + "')")
public void setAssigneeForList(@PathVariable(DOSSIER_ID) String dossierId,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId,
@RequestBody List<String> fileIds) {
fileIds.forEach(fileId -> setCurrentFileAssignee(dossierId, fileId, assigneeId));
}
@Override
@PreAuthorize("hasAuthority('" + SET_REVIEWER + "')")
public void setStatusUnderReviewForList(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody List<String> fileIds,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId) {
fileIds.forEach(fileId -> setStatusUnderReview(dossierId, fileId, assigneeId));
}
private void setStatusUnderReviewForFile(String dossierId, String fileId, String assigneeId) {
accessControlService.verifyUserIsMemberOrApprover(dossierId);
fileStatusManagementService.setStatusUnderReview(dossierId, fileId, assigneeId);
}
private void generatePossibleUnassignedFromFileNotification(String dossierId, String fileId, FileModel oldFileStatus, String newAssigneeId) {
if (oldFileStatus.getAssignee() != null && !oldFileStatus.getAssignee().equals(newAssigneeId) && !KeycloakSecurity.getUserId().equals(oldFileStatus.getAssignee())) {
notificationPersistenceService.insertNotification(AddNotificationRequest.builder()
.userId(oldFileStatus.getAssignee())
.issuerId(KeycloakSecurity.getUserId())
.notificationType(NotificationType.UNASSIGNED_FROM_FILE.name())
.target(Map.of(DOSSIER_ID, dossierId, FILE_ID, fileId, FILE_NAME, oldFileStatus.getFilename()))
.build());
}
}
@Override
@PreAuthorize("hasAuthority('" + SET_STATUS_UNDER_APPROVAL + "')")
public void setStatusUnderApprovalForList(@PathVariable(DOSSIER_ID) String dossierId,
@RequestBody List<String> fileIds,
@RequestParam(value = ASSIGNEE_ID_REQUEST_PARAM, required = false) String assigneeId) {
fileIds.forEach(fileId -> setStatusUnderApproval(dossierId, fileId, assigneeId));
}
private void setStatusUnderApprovalForFile(String dossierId, String fileId, String assigneeId) {
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
var dossier = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, false, false));
if (assigneeId != null && !dossier.getApproverIds().contains(assigneeId)) {
throw new BadRequestException("Approver is not valid");
}
fileStatusManagementService.setStatusUnderApproval(dossierId, fileId, assigneeId);
}
@Override
@PreAuthorize("hasAuthority('" + SET_STATUS_APPROVED + "')")
public void setStatusApprovedForList(String dossierId, List<String> fileIds) {
accessControlService.verifyUserIsApprover(dossierId);
dossierManagementService.getDossierById(dossierId, false, false);
fileIds.forEach(fileId -> setStatusApproved(dossierId, fileId));
}
@PreAuthorize("hasAuthority('" + SET_REVIEWER + "')")
public void setStatusNewForList(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody List<String> fileIds) {
for (var fileId : fileIds) {
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
var fileStatus = fileStatusManagementService.getFileStatus(fileId);
if (!WorkflowStatus.UNDER_REVIEW.equals(fileStatus.getWorkflowStatus())) {
log.debug("Transition to NEW status is not possible from: " + fileStatus.getWorkflowStatus());
break;
} else {
fileStatusManagementService.setCurrentFileAssignee(dossierId, fileId, null);
fileStatusManagementService.setStatusNew(dossierId, fileId);
generatePossibleUnassignedFromFileNotification(dossierId, fileId, fileStatus, null);
}
}
}
@PreAuthorize("hasAuthority('" + READ_FILE_STATUS + "')")
public List<FileStatus> getSoftDeletedDossierStatus(@PathVariable(DOSSIER_ID) String dossierId) {
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
return fileStatusManagementService.getSoftDeletedDossierStatus(dossierId).stream().map(this::convert).collect(Collectors.toList());
} catch (AccessDeniedException e) {
return new ArrayList<>();
}
}
private void setStatusApprovedForFile(String dossierId, String fileId) {
var fileStatus = getFileStatus(dossierId, fileId);
if (fileStatus.isHasSuggestions()) {
throw new NotAllowedException("File contains unapproved requests.");
}
if (!fileStatus.getProcessingStatus().equals(ProcessingStatus.PROCESSED)) {
throw new NotAllowedException("File is not processed.");
}
fileStatusManagementService.setStatusApproved(dossierId, fileId, KeycloakSecurity.getUserId());
}
private FileStatus convert(FileModel status) {
return FileStatus.builder()
.dossierId(status.getDossierId())
.dossierArchived(status.isDossierArchived())
.dossierStatusId(status.getDossierStatusId())
.dossierTemplateId(status.getDossierTemplateId())
.fileId(status.getId())
.filename(status.getFilename())
.processingStatus(ProcessingStatus.valueOf(status.getProcessingStatus().name()))
.workflowStatus(WorkflowStatus.valueOf(status.getWorkflowStatus().name()))
.numberOfPages(status.getNumberOfPages())
.added(status.getAdded())
.lastUpdated(status.getLastUpdated())
.numberOfAnalyses(status.getNumberOfAnalyses())
.assignee(status.getAssignee())
.lastReviewer(status.getLastReviewer())
.lastApprover(status.getLastApprover())
.hasUpdates(status.isHasUpdates())
.hasImages(status.isHasImages())
.hasRequests(status.isHasSuggestions())
.hasSuggestions(status.isHasSuggestions())
.excludedFromAutomaticAnalysis(status.isExcludedFromAutomaticAnalysis())
.hasHints(status.isHasHints())
.hasRedactions(status.isHasRedactions())
.ocrEndTime(status.getOcrEndTime())
.ocrStartTime(status.getOcrStartTime())
.numberOfOCRedPages(status.getNumberOfOCRedPages() != null ? status.getNumberOfOCRedPages() : 0)
.numberOfPagesToOCR(status.getNumberOfPagesToOCR() != null ? status.getNumberOfPagesToOCR() : 0)
.hasAnnotationComments(status.isHasAnnotationComments())
.uploader(status.getUploader())
.dictionaryVersion(status.getDictionaryVersion())
.rulesVersion(status.getRulesVersion())
.legalBasisVersion(status.getLegalBasisVersion())
.lastProcessed(status.getLastProcessed())
.approvalDate(status.getApprovalDate())
.lastUploaded(status.getLastUploaded())
.analysisDuration(status.getAnalysisDuration())
.fileAttributes(new FileAttributes(status.getFileAttributes()))
.dossierDictionaryVersion(status.getDossierDictionaryVersion())
.excluded(status.isExcluded())
.excludedPages(status.getExcludedPages())
.softDeletedTime(status.getDeleted())
.analysisRequired(status.isAnalysisRequired())
.lastFileAttributeChange(status.getLastFileAttributeChange())
.redactionModificationDate(status.getRedactionModificationDate())
.fileManipulationDate(status.getFileManipulationDate())
.lastManualChangeDate(status.getLastManualChangeDate())
.hasHighlights(status.isHasHighlights())
.lastIndexed(status.getLastIndexed())
.fileSize(status.getFileSize())
.build();
}
}

View File

@ -0,0 +1,74 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.READ_DOSSIER;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.client.redactionreportservice.StatusReportClient;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.service.v1.api.external.resource.StatusReportResource;
import com.iqser.red.service.redaction.report.v1.api.model.StatusReportResponse;
import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class StatusReportController implements StatusReportResource {
private final StatusReportClient statusReportClient;
private final AccessControlService accessControlService;
@Override
@PreAuthorize("hasAuthority('" + READ_DOSSIER + "')")
public ResponseEntity<?> generateStatusReport(@PathVariable(DOSSIER_ID) String dossierId) {
try {
accessControlService.verifyUserHasViewPermissions(dossierId);
} catch (AccessDeniedException e) {
throw new NotFoundException("Object not found");
}
try {
StatusReportResponse statusReportResponse = statusReportClient.generateStatusReport(dossierId);
return getResponseEntity(statusReportResponse.getReport(), statusReportResponse.getFilename(), MediaType.APPLICATION_OCTET_STREAM);
} catch (FeignException e) {
throw processFeignException(e);
}
}
private ResponseEntity<?> getResponseEntity(byte[] file, String filename, MediaType mediaType) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(mediaType);
if (filename != null) {
httpHeaders.add("Content-Disposition", "attachment; filename*=utf-8''" + StringEncodingUtils.urlEncode(filename));
}
InputStream is = new ByteArrayInputStream(file);
return new ResponseEntity<>(new InputStreamResource(is), httpHeaders, HttpStatus.OK);
}
}

View File

@ -0,0 +1,56 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.*;
import java.util.List;
import javax.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.DeploymentKeyService;
import com.iqser.red.service.persistence.management.v1.processor.service.TenantManagementService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.TenantsResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.TenantRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.multitenancy.TenantResponse;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
public class TenantsController implements TenantsResource {
private final TenantManagementService tenantManagementService;
private final DeploymentKeyService deploymentKeyService;
@PreAuthorize("hasAuthority('" + CREATE_TENANT + "')")
public void createTenant(@Valid @RequestBody TenantRequest tenantRequest) {
try {
tenantManagementService.createTenant(tenantRequest);
} catch (IllegalArgumentException e) {
throw new BadRequestException(e.getMessage(), e);
}
}
@PreAuthorize("hasAuthority('" + GET_TENANTS + "')")
public List<TenantResponse> getTenants() {
return tenantManagementService.getTenants();
}
@PreAuthorize("hasAuthority('" + DEPLOYMENT_INFO + "')")
public JSONPrimitive<String> getDeploymentKey(@PathVariable(TENANT_ID_PARAM) String tenantId) {
return JSONPrimitive.of(deploymentKeyService.getDeploymentKey(tenantId));
}
}

View File

@ -0,0 +1,264 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.iqser.red.keycloak.commons.KeycloakSecurity;
import com.iqser.red.service.pdftron.redaction.v1.api.model.ByteContentDocument;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisService;
import com.iqser.red.service.persistence.management.v1.processor.service.UploadService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.FileUtils;
import com.iqser.red.service.persistence.service.v1.api.external.resource.UploadResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileUploadResult;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import feign.FeignException;
import io.micrometer.core.annotation.Timed;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequiredArgsConstructor
@Slf4j
public class UploadController implements UploadResource {
private static final int THRESHOLD_ENTRIES = 10000;
private static final int THRESHOLD_SIZE = 1000000000; // 1 GB
private static final double THRESHOLD_RATIO = 10;
private static final List<String> VALID_FILE_EXTENSIONS = List.of("pdf", "docx", "doc", "xls", "xlsx", "ppt", "pptx");
private final UploadService uploadService;
private final ReanalysisService reanalysisService;
private final AccessControlService accessControlService;
private final AuditPersistenceService auditPersistenceService;
@Timed
@Override
public FileUploadResult upload(@RequestPart(name = "file") MultipartFile file,
@PathVariable(DOSSIER_ID) String dossierId,
@RequestParam(value = "keepManualRedactions", required = false, defaultValue = "false") boolean keepManualRedactions) {
if (file.getOriginalFilename() == null) {
throw new BadRequestException("Could not upload file, no filename provided.");
}
var extension = getExtension(file.getOriginalFilename());
try {
switch (extension) {
case "zip":
return handleZip(dossierId, file.getBytes(), keepManualRedactions);
case "csv":
return uploadService.importCsv(dossierId, file.getBytes());
default:
if (VALID_FILE_EXTENSIONS.contains(extension)) {
return uploadService.processSingleFile(dossierId, file.getOriginalFilename(), file.getBytes(), keepManualRedactions);
} else {
throw new BadRequestException("Invalid file uploaded");
}
}
} catch (IOException e) {
throw new BadRequestException(e.getMessage(), e);
}
}
public void importRedactions(@RequestPart(name = "file") MultipartFile file,
@PathVariable(DOSSIER_ID) String dossierId,
@PathVariable(FILE_ID) String fileId,
@RequestParam(value = "pageInclusionRequest", required = false) Set<Integer> pageInclusionRequest) {
accessControlService.verifyFileIsNotApproved(dossierId, fileId);
accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);
try {
reanalysisService.importRedactions(ByteContentDocument.builder().dossierId(dossierId).fileId(fileId).document(file.getBytes()).pages(pageInclusionRequest).build());
auditPersistenceService.audit(AuditRequest.builder()
.userId(KeycloakSecurity.getUserId())
.objectId(fileId)
.category(AuditCategory.DOCUMENT.name())
.message("Redactions were imported")
.details(Map.of("dossierId", dossierId))
.build());
} catch (IOException e) {
throw new BadRequestException(e.getMessage(), e);
} catch (FeignException e) {
throw processFeignException(e);
}
}
private String getExtension(String fileName) {
return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
}
private FileUploadResult handleZip(String dossierId, byte[] fileContent, boolean keepManualRedactions) throws IOException {
File tempFile = FileUtils.createTempFile(UUID.randomUUID().toString(), ".zip");
try (var fileOutputStream = new FileOutputStream(tempFile)) {
IOUtils.write(fileContent, fileOutputStream);
}
try {
checkForSymlinks(tempFile);
var zipData = unzip(tempFile, dossierId, keepManualRedactions);
if (zipData.csvBytes != null) {
try {
var importResult = uploadService.importCsv(dossierId, zipData.csvBytes);
zipData.fileUploadResult.getProcessedAttributes().addAll(importResult.getProcessedAttributes());
zipData.fileUploadResult.getProcessedFileIds().addAll(importResult.getProcessedFileIds());
} catch (Exception e) {
log.debug("CSV file inside ZIP failed", e);
// TODO return un-processed files to client
}
}
return zipData.fileUploadResult;
} finally {
boolean isDeleted = tempFile.delete();
if (!isDeleted) {
log.warn("tempFile could not be deleted");
}
}
}
private void checkForSymlinks(File tempFile) throws IOException {
try (var fis = new FileInputStream(tempFile); var zipFile = new ZipFile(fis.getChannel())) {
for (var entryEnum = zipFile.getEntries(); entryEnum.hasMoreElements(); ) {
var ze = entryEnum.nextElement();
if (ze.isUnixSymlink()) {
throw new BadRequestException("ZIP-files with symlinks are not allowed");
}
}
}
}
private ZipData unzip(File tempFile, String dossierId, boolean keepManualRedactions) throws IOException {
var zipData = new ZipData();
try (var fis = new FileInputStream(tempFile); var zipFile = new ZipFile(fis.getChannel())) {
for (var entryEnum = zipFile.getEntries(); entryEnum.hasMoreElements(); ) {
var ze = entryEnum.nextElement();
zipData.totalEntryArchive++;
if (!ze.isDirectory()) {
processFileZipEntry(ze, zipFile, dossierId, keepManualRedactions, zipData);
}
}
}
return zipData;
}
private void processFileZipEntry(ZipArchiveEntry ze, ZipFile zipFile, String dossierId, boolean keepManualRedactions, ZipData zipData) throws IOException {
var extension = getExtension(ze.getName());
final String fileName;
if (ze.getName().lastIndexOf("/") >= 0) {
fileName = ze.getName().substring(ze.getName().lastIndexOf("/") + 1);
} else {
fileName = ze.getName();
}
if (fileName.startsWith(".")) {
return;
}
var entryAsBytes = readCurrentZipEntry(ze, zipFile);
zipData.totalSizeArchive += entryAsBytes.length;
// 1. the uncompressed data size is too much for the application resource capacity
// 2. too many entries in the archive can lead to inode exhaustion of the file-system
if (zipData.totalSizeArchive > THRESHOLD_SIZE || zipData.totalEntryArchive > THRESHOLD_ENTRIES) {
throw new BadRequestException("ZIP-Bomb detected.");
}
if ("csv".equals(extension)) {
zipData.csvBytes = entryAsBytes;
} else if (VALID_FILE_EXTENSIONS.contains(extension)) {
try {
var result = uploadService.processSingleFile(dossierId, fileName, entryAsBytes, keepManualRedactions);
zipData.fileUploadResult.getFileIds().addAll(result.getFileIds());
} catch (Exception e) {
log.debug("PDF File inside ZIP failed", e);
// TODO return un-processed files to client
}
}
}
private byte[] readCurrentZipEntry(ZipArchiveEntry ze, ZipFile zipFile) throws IOException {
var bos = new ByteArrayOutputStream();
try (var entryStream = zipFile.getInputStream(ze)) {
var buffer = new byte[2048];
var nBytes = 0;
int totalSizeEntry = 0;
while ((nBytes = entryStream.read(buffer)) > 0) {
bos.write(buffer, 0, nBytes);
totalSizeEntry += nBytes;
double compressionRatio = (float) totalSizeEntry / ze.getCompressedSize();
if (compressionRatio > THRESHOLD_RATIO) {
// ratio between compressed and uncompressed data is highly suspicious, looks like a Zip Bomb Attack
throw new BadRequestException("ZIP-Bomb detected.");
}
}
}
return bos.toByteArray();
}
@FieldDefaults(level = AccessLevel.PUBLIC)
private static final class ZipData {
byte[] csvBytes;
int totalSizeArchive;
int totalEntryArchive;
FileUploadResult fileUploadResult = new FileUploadResult();
}
}

View File

@ -0,0 +1,147 @@
package com.iqser.red.persistence.service.v1.external.api.impl.controller;
import static com.iqser.red.keycloak.commons.roles.ActionRoles.*;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.iqser.red.keycloak.commons.KeyCloakSettings;
import com.iqser.red.keycloak.commons.model.User;
import com.iqser.red.service.persistence.management.v1.processor.exception.AuthenticationFailedException;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.UserService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.UserResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.CreateUserRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.ResetPasswordRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.UpdateMyProfileRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.UpdateProfileRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class UserController implements UserResource {
private final UserService userService;
private final KeyCloakSettings keyCloakSettings;
@Override
@PreAuthorize("hasAuthority('" + READ_USERS + "')")
public List<User> getAllRedUsers(@RequestParam(name = REFRESH_CACHE_PARAM, defaultValue = "false", required = false) boolean bypassCache) {
if (bypassCache) {
userService.evictUserCache();
}
return userService.getAllUsers()
.stream()
.filter(user -> user.getRoles().stream().anyMatch(r -> r.startsWith(keyCloakSettings.getRolePrefix())))
.collect(Collectors.toList());
}
@Override
@PreAuthorize("hasAuthority('" + READ_ALL_USERS + "')")
public List<User> getAllUsers(@RequestParam(name = REFRESH_CACHE_PARAM, defaultValue = "false", required = false) boolean bypassCache) {
if (bypassCache) {
userService.evictUserCache();
}
return userService.getAllUsers();
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_USERS + "')")
public void updateProfile(@PathVariable(USER_ID) String userId, @RequestBody UpdateProfileRequest updateProfileRequest) {
this.userService.updateProfile(userId, updateProfileRequest);
}
@Override
@PreAuthorize("hasAuthority('" + UPDATE_MY_PROFILE + "')")
public void updateMyProfile(@Valid @RequestBody UpdateMyProfileRequest updateProfileRequest) {
try {
this.userService.updateMyProfile(updateProfileRequest);
} catch (AuthenticationFailedException e) {
throw new BadRequestException(e.getMessage(), e);
}
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_USERS + "')")
public void deleteUsers(@RequestParam(USER_ID) List<String> userIds) {
userIds.forEach(this::deleteUser);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_USERS + "')")
public void deleteUser(@PathVariable(USER_ID) String userId) {
userService.deleteUser(userId);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_USERS + "')")
public User createUser(@RequestBody CreateUserRequest user) {
return this.userService.createUser(user);
}
@Override
@PreAuthorize("hasAuthority('" + READ_USERS + "')")
public User getUserById(@PathVariable(USER_ID) String userId) {
if (StringUtils.isEmpty(userId)) {
throw new BadRequestException("The userId should not be empty.");
}
return userService.getUserById(userId).orElseThrow(() -> new NotFoundException("User not found"));
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_USERS + "')")
public void setRoles(@PathVariable(USER_ID) String userId, @RequestBody List<String> roles) {
userService.setRoles(userId, roles);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_USERS + "')")
public void resetPassword(@PathVariable(USER_ID) String userId, @RequestBody ResetPasswordRequest resetPasswordRequest) {
this.userService.resetPassword(userId, resetPasswordRequest);
}
@Override
@PreAuthorize("hasAuthority('" + WRITE_USERS + "')")
public User activateProfile(@PathVariable(USER_ID) String userId, @RequestParam(IS_ACTIVE_PARAM) boolean isActive) {
return this.userService.activateProfile(userId, isActive);
}
}

Some files were not shown because too many files have changed in this diff Show More