Adding new annotation service

This commit is contained in:
Thomas Georgios Giannos 2024-02-09 18:50:34 +02:00
parent 075573ffaf
commit 5c98648e56
213 changed files with 13344 additions and 0 deletions

View File

@ -0,0 +1,27 @@
####################################### Build stage #######################################
FROM maven:3.9-eclipse-temurin-17-alpine AS build-stage
ARG CITE_MAVEN_ACCOUNT_USR
ARG CITE_MAVEN_ACCOUNT_PSW
COPY pom.xml /build/
COPY annotation /build/annotation/
COPY annotation-web /build/annotation-web/
COPY settings.xml /root/.m2/settings.xml
RUN rm -f /build/annotation-web/src/main/resources/config/app.env
RUN rm -f /build/annotation-web/src/main/resources/config/*-dev.yml
RUN rm -f /build/annotation-web/src/main/resources/logging/*.xml
RUN rm -f /build/annotation-web/src/main/resources/certificates/*.crt
WORKDIR /build/
RUN mvn -Dserver.username=${CITE_MAVEN_ACCOUNT_USR} -Dserver.password=${CITE_MAVEN_ACCOUNT_PSW} dependency:go-offline
# Build project
RUN mvn -Dserver.username=${CITE_MAVEN_ACCOUNT_USR} -Dserver.password=${CITE_MAVEN_ACCOUNT_PSW} clean package
######################################## Run Stage ########################################
FROM eclipse-temurin:17-jre-ubi9-minimal
COPY --from=build-stage /build/annotation-web/target/sti-viewer-annotation-web.jar /app/sti-viewer-annotation-web.jar
ENTRYPOINT ["java","-Dspring.config.additional-location=file:/config/","-Dspring.profiles.active=${PROFILE}","-Djava.security.egd=file:/dev/./urandom","-jar","/app/sti-viewer-annotation-web.jar"]

View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>gr.cite</groupId>
<artifactId>annotation-service-parent</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>annotation-web</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>annotation</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>oidc-authz</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>cache</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>exceptions-web</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>cors-web</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<finalName>argos-annotation-web</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,26 @@
package gr.cite.notification.web;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication(
scanBasePackages = {
"eu.eudat",
"gr.cite",
"gr.cite.queueoutbox",
"gr.cite.queueinbox",
"gr.cite.notification.integrationevent",
"gr.cite.tools",
"gr.cite.commons"})
@EntityScan({
"gr.cite.notification.data"})
@EnableAsync
public class AnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(AnnotationApplication.class, args);
}
}

View File

@ -0,0 +1,40 @@
package gr.cite.notification.web;
import gr.cite.notification.web.interceptors.UserInterceptor;
import gr.cite.notification.web.scope.tenant.TenantInterceptor;
import gr.cite.notification.web.scope.tenant.TenantScopeClaimInterceptor;
import gr.cite.notification.web.scope.tenant.TenantScopeHeaderInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
private final TenantInterceptor tenantInterceptor;
private final TenantScopeHeaderInterceptor scopeHeaderInterceptor;
private final TenantScopeClaimInterceptor scopeClaimInterceptor;
private final UserInterceptor userInterceptor;
@Autowired
public WebConfiguration(
TenantInterceptor tenantInterceptor,
TenantScopeHeaderInterceptor scopeHeaderInterceptor,
TenantScopeClaimInterceptor scopeClaimInterceptor,
UserInterceptor userInterceptor
) {
this.tenantInterceptor = tenantInterceptor;
this.scopeHeaderInterceptor = scopeHeaderInterceptor;
this.scopeClaimInterceptor = scopeClaimInterceptor;
this.userInterceptor = userInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
int order = 1;
registry.addWebRequestInterceptor(scopeHeaderInterceptor).order(order++);
registry.addWebRequestInterceptor(scopeClaimInterceptor).order(order++);
registry.addWebRequestInterceptor(userInterceptor).order(order++);
registry.addWebRequestInterceptor(tenantInterceptor).order(order++);
}
}

View File

@ -0,0 +1,44 @@
package gr.cite.notification.web.authorization;
import gr.cite.commons.web.authz.handler.AuthorizationHandler;
import gr.cite.commons.web.authz.handler.AuthorizationHandlerContext;
import gr.cite.commons.web.authz.policy.AuthorizationRequirement;
import gr.cite.commons.web.oidc.principal.MyPrincipal;
import gr.cite.notification.authorization.OwnedAuthorizationRequirement;
import gr.cite.notification.authorization.OwnedResource;
import gr.cite.notification.common.scope.user.UserScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("ownedAuthorizationHandler")
public class OwnedAuthorizationHandler extends AuthorizationHandler<OwnedAuthorizationRequirement> {
private final UserScope userScope;
@Autowired
public OwnedAuthorizationHandler(UserScope userScope) {
this.userScope = userScope;
}
@Override
public int handleRequirement(AuthorizationHandlerContext context, Object resource, AuthorizationRequirement requirement) {
OwnedAuthorizationRequirement req = (OwnedAuthorizationRequirement) requirement;
OwnedResource rs = (OwnedResource) resource;
boolean isAuthenticated = ((MyPrincipal) context.getPrincipal()).isAuthenticated();
if (!isAuthenticated) return ACCESS_NOT_DETERMINED;
if (this.userScope.getUserIdSafe() == null) return ACCESS_NOT_DETERMINED;
if (rs != null && rs.getUserIds() != null && rs.getUserIds().contains(this.userScope.getUserIdSafe())) return ACCESS_GRANTED;
return ACCESS_NOT_DETERMINED;
}
@Override
public Class<? extends AuthorizationRequirement> supporting() {
return OwnedAuthorizationRequirement.class;
}
}

View File

@ -0,0 +1,25 @@
package gr.cite.notification.web.config;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class AppMessageSourceConfiguration {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages/messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
@Bean
public LocalValidatorFactoryBean getValidator() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
}

View File

@ -0,0 +1,142 @@
package gr.cite.notification.web.config;
import gr.cite.commons.web.authz.handler.AuthorizationHandler;
import gr.cite.commons.web.authz.handler.PermissionClientAuthorizationHandler;
import gr.cite.commons.web.authz.policy.AuthorizationRequirement;
import gr.cite.commons.web.authz.policy.AuthorizationRequirementMapper;
import gr.cite.commons.web.authz.policy.AuthorizationResource;
import gr.cite.commons.web.authz.policy.resolver.AuthorizationPolicyConfigurer;
import gr.cite.commons.web.authz.policy.resolver.AuthorizationPolicyResolverStrategy;
import gr.cite.commons.web.oidc.configuration.WebSecurityProperties;
import gr.cite.notification.authorization.OwnedAuthorizationRequirement;
import gr.cite.notification.authorization.OwnedResource;
import gr.cite.notification.web.authorization.OwnedAuthorizationHandler;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import java.util.List;
import java.util.Set;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
private final WebSecurityProperties webSecurityProperties;
private final AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
private final Filter apiKeyFilter;
private final OwnedAuthorizationHandler ownedAuthorizationHandler;
@Autowired
public SecurityConfiguration(WebSecurityProperties webSecurityProperties,
@Qualifier("tokenAuthenticationResolver") AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver,
@Qualifier("apiKeyFilter") Filter apiKeyFilter,
@Qualifier("ownedAuthorizationHandler") OwnedAuthorizationHandler ownedAuthorizationHandler) {
this.webSecurityProperties = webSecurityProperties;
this.authenticationManagerResolver = authenticationManagerResolver;
this.apiKeyFilter = apiKeyFilter;
this.ownedAuthorizationHandler = ownedAuthorizationHandler;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
HttpSecurity tempHttp = http
.csrf(AbstractHttpConfigurer::disable)
.cors(httpSecurityCorsConfigurer -> {})
.headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
.addFilterBefore(apiKeyFilter, AbstractPreAuthenticatedProcessingFilter.class)
.authorizeHttpRequests(authRequest ->
authRequest.requestMatchers(buildAntPatterns(webSecurityProperties.getAllowedEndpoints())).anonymous()
.requestMatchers(buildAntPatterns(webSecurityProperties.getAuthorizedEndpoints())).authenticated())
.sessionManagement( sessionManagementConfigurer-> sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.NEVER))
.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver));
return tempHttp.build();
}
@Bean
AuthorizationPolicyConfigurer authorizationPolicyConfigurer() {
return new AuthorizationPolicyConfigurer() {
@Override
public AuthorizationPolicyResolverStrategy strategy() {
return AuthorizationPolicyResolverStrategy.STRICT_CONSENSUS_BASED;
}
//Here you can register your custom authorization handlers, which will get used as well as the existing ones
//This is optional and can be omitted
//If not set / set to null, only the default authorization handlers will be used
@Override
public List<AuthorizationHandler<? extends AuthorizationRequirement>> addCustomHandlers() {
return List.of(ownedAuthorizationHandler);
}
//Here you can register your custom authorization requirements (if any)
//This is optional and can be omitted
//If not set / set to null, only the default authorization requirements will be used
@Override
public List<? extends AuthorizationRequirement> extendRequirements() {
return List.of(
// new TimeOfDayAuthorizationRequirement(new TimeOfDay("08:00","16:00"), true)
);
}
//Here you can select handlers you want to disable by providing the classes they are implemented by
//You can disable any handler (including any custom one)
//This is optional and can be omitted
//If not set / set to null, all the handlers will be invoked, based on their requirement support
//In the example below, the default client handler will be ignored by the resolver
@Override
public List<Class<? extends AuthorizationHandler<? extends AuthorizationRequirement>>> disableHandlers() {
return List.of(PermissionClientAuthorizationHandler.class);
}
};
}
@Bean
AuthorizationRequirementMapper authorizationRequirementMapper() {
return new AuthorizationRequirementMapper() {
@Override
public AuthorizationRequirement map(AuthorizationResource resource, boolean matchAll, String[] permissions) {
Class<?> type = resource.getClass();
if (!AuthorizationResource.class.isAssignableFrom(type)) throw new IllegalArgumentException("resource");
if (OwnedResource.class.equals(type)) {
return new OwnedAuthorizationRequirement();
}
throw new IllegalArgumentException("resource");
}
};
}
private String[] buildAntPatterns(Set<String> endpoints) {
if (endpoints == null) {
return new String[0];
}
return endpoints.stream()
.filter(endpoint -> endpoint != null && !endpoint.isBlank())
.map(endpoint -> "/" + stripUnnecessaryCharacters(endpoint) + "/**")
.toArray(String[]::new);
}
private String stripUnnecessaryCharacters(String endpoint) {
endpoint = endpoint.strip();
if (endpoint.startsWith("/")) {
endpoint = endpoint.substring(1);
}
if (endpoint.endsWith("/")) {
endpoint = endpoint.substring(0, endpoint.length() - 1);
}
return endpoint;
}
}

View File

@ -0,0 +1,193 @@
package gr.cite.notification.web.controllerhandler;
import gr.cite.notification.common.JsonHandlingService;
import gr.cite.tools.exception.*;
import gr.cite.tools.logging.LoggerService;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import java.text.MessageFormat;
import java.util.Map;
@RestControllerAdvice
@ControllerAdvice
public class GlobalExceptionHandler {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(GlobalExceptionHandler.class));
private final JsonHandlingService jsonHandlingService;
public GlobalExceptionHandler(JsonHandlingService jsonHandlingService) {
this.jsonHandlingService = jsonHandlingService;
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleUnexpectedErrors(Exception exception, WebRequest request) throws Exception {
HandledException handled = this.handleException(exception, request);
this.log(handled.getLevel(), exception, MessageFormat.format("returning code {0} and payload {1}", handled.getStatusCode(), handled.getMessage()));
return new ResponseEntity<>(handled.getMessage(), handled.getStatusCode());
}
public void log(System.Logger.Level level, Exception e, String message) {
if (level != null) {
switch (level) {
case TRACE:
logger.trace(message, e);
break;
case DEBUG:
logger.debug(message, e);
break;
case INFO:
logger.info(message, e);
break;
case WARNING:
logger.warn(message, e);
break;
case ERROR:
logger.error(message, e);
break;
}
}
}
public HandledException handleException(Exception exception, WebRequest request) throws Exception {
HttpStatus statusCode;
Map<String, Object> result;
System.Logger.Level logLevel;
switch (exception){
case MyNotFoundException myNotFoundException -> {
logLevel = System.Logger.Level.DEBUG;
statusCode = HttpStatus.NOT_FOUND;
int code = myNotFoundException.getCode();
if (code > 0) {
result = Map.ofEntries(
Map.entry("code", code),
Map.entry("error", myNotFoundException.getMessage())
);
}
else {
result = Map.ofEntries(
Map.entry("error", myNotFoundException.getMessage())
);
}
}
case MyUnauthorizedException myUnauthorizedException -> {
logLevel = System.Logger.Level.DEBUG;
statusCode = HttpStatus.UNAUTHORIZED;
int code = myUnauthorizedException.getCode();
if (code > 0) {
result = Map.ofEntries(
Map.entry("code", code),
Map.entry("error", myUnauthorizedException.getMessage())
);
}
else {
result = Map.ofEntries(
Map.entry("error", myUnauthorizedException.getMessage())
);
}
}
case MyForbiddenException myForbiddenException -> {
logLevel = System.Logger.Level.DEBUG;
statusCode = HttpStatus.FORBIDDEN;
int code = myForbiddenException.getCode();
if (code > 0) {
result = Map.ofEntries(
Map.entry("code", code),
Map.entry("error", myForbiddenException.getMessage())
);
}
else {
result = Map.ofEntries(
Map.entry("error", myForbiddenException.getMessage())
);
}
}
case MyValidationException myValidationException -> {
logLevel = System.Logger.Level.DEBUG;
statusCode = HttpStatus.BAD_REQUEST;
int code = myValidationException.getCode();
if (code > 0) {
result = Map.ofEntries(
Map.entry("code", code),
Map.entry("error", myValidationException.getMessage()),
Map.entry("message", myValidationException.getErrors())
);
}
else {
result = Map.ofEntries(
Map.entry("error", myValidationException.getMessage()),
Map.entry("message", myValidationException.getErrors())
);
}
}
case MyApplicationException myApplicationException -> {
logLevel = System.Logger.Level.ERROR;
statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
int code = myApplicationException.getCode();
if (code > 0) {
result = Map.ofEntries(
Map.entry("code", code),
Map.entry("error", myApplicationException.getMessage())
);
}
else {
result = Map.ofEntries(
Map.entry("error", myApplicationException.getMessage())
);
}
}
default -> {
logLevel = System.Logger.Level.ERROR;
statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
result = Map.ofEntries(
Map.entry("error", "System error")
);
}
};
String serialization = this.jsonHandlingService.toJsonSafe(result);
return new HandledException(statusCode, serialization, logLevel);
}
public static class HandledException{
public HttpStatus statusCode;
public String message;
public System.Logger.Level level;
public HandledException(HttpStatus statusCode, String message, System.Logger.Level level) {
this.statusCode = statusCode;
this.message = message;
this.level = level;
}
public HttpStatus getStatusCode() {
return statusCode;
}
public void setStatusCode(HttpStatus statusCode) {
this.statusCode = statusCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public System.Logger.Level getLevel() {
return level;
}
public void setLevel(System.Logger.Level level) {
this.level = level;
}
}
}

View File

@ -0,0 +1,91 @@
package gr.cite.notification.web.controllers;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.MyPrincipal;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.web.model.Account;
import gr.cite.notification.web.model.AccountBuilder;
import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.LoggerService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping(path = "api/principal", produces = MediaType.APPLICATION_JSON_VALUE)
public class PrincipalController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(PrincipalController.class));
private final AuditService auditService;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final AccountBuilder accountBuilder;
private final ClaimExtractor claimExtractor;
@Autowired
public PrincipalController(
CurrentPrincipalResolver currentPrincipalResolver,
AccountBuilder accountBuilder,
AuditService auditService,
ClaimExtractor claimExtractor) {
this.currentPrincipalResolver = currentPrincipalResolver;
this.accountBuilder = accountBuilder;
this.auditService = auditService;
this.claimExtractor = claimExtractor;
}
@GetMapping("me")
public Account me(FieldSet fieldSet) {
logger.debug("me");
if (fieldSet == null || fieldSet.isEmpty()) {
fieldSet = new BaseFieldSet(
Account._isAuthenticated,
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._subject),
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._userId),
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._name),
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._scope),
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._client),
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._issuedAt),
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._notBefore),
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._authenticatedAt),
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._expiresAt),
BaseFieldSet.asIndexer(Account._principal, Account.PrincipalInfo._more),
Account._permissions);
}
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
Account me = this.accountBuilder.build(fieldSet, principal);
this.auditService.track(AuditableAction.Principal_Lookup);
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
return me;
}
@GetMapping("my-tenants")
public List<String> myTenants() {
logger.debug("my-tenants");
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
List<String> tenants = this.claimExtractor.asStrings(principal, TenantScope.TenantCodesClaimName);
this.auditService.track(AuditableAction.Tenants_Lookup);
//auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
return tenants == null ? null : tenants.stream().distinct().collect(Collectors.toList());
}
}

View File

@ -0,0 +1,129 @@
package gr.cite.notification.web.controllers;
import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.authorization.AuthorizationFlags;
import gr.cite.notification.common.enums.TenantConfigurationType;
import gr.cite.notification.data.TenantConfigurationEntity;
import gr.cite.notification.model.TenantConfiguration;
import gr.cite.notification.model.builder.TenantConfigurationBuilder;
import gr.cite.notification.model.censorship.TenantConfigurationCensor;
import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationEmailClientPersist;
import gr.cite.notification.query.TenantConfigurationQuery;
import gr.cite.notification.query.lookup.TenantConfigurationLookup;
import gr.cite.notification.service.tenantconfiguration.TenantConfigurationService;
import gr.cite.notification.web.model.QueryResult;
import gr.cite.tools.auditing.AuditService;
import gr.cite.tools.data.builder.BuilderFactory;
import gr.cite.tools.data.censor.CensorFactory;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.exception.MyNotFoundException;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry;
import gr.cite.tools.validation.ValidationFilterAnnotation;
import jakarta.transaction.Transactional;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.bind.annotation.*;
import javax.management.InvalidApplicationException;
import java.util.*;
@RestController
@RequestMapping(path = "api/notification/tenant-configuration")
public class TenantConfigurationController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantConfigurationController.class));
private final BuilderFactory builderFactory;
private final AuditService auditService;
private final TenantConfigurationService tenantConfigurationService;
private final CensorFactory censorFactory;
private final QueryFactory queryFactory;
private final MessageSource messageSource;
@Autowired
public TenantConfigurationController(BuilderFactory builderFactory,
AuditService auditService,
TenantConfigurationService tenantConfigurationService, CensorFactory censorFactory,
QueryFactory queryFactory,
MessageSource messageSource) {
this.builderFactory = builderFactory;
this.auditService = auditService;
this.tenantConfigurationService = tenantConfigurationService;
this.censorFactory = censorFactory;
this.queryFactory = queryFactory;
this.messageSource = messageSource;
}
@PostMapping("query")
public QueryResult<TenantConfiguration> query(@RequestBody TenantConfigurationLookup lookup) throws MyApplicationException, MyForbiddenException {
logger.debug("querying {}", TenantConfiguration.class.getSimpleName());
this.censorFactory.censor(TenantConfigurationCensor.class).censor(lookup.getProject());
TenantConfigurationQuery query = lookup.enrich(this.queryFactory);
List<TenantConfigurationEntity> data = query.collectAs(lookup.getProject());
List<TenantConfiguration> models = this.builderFactory.builder(TenantConfigurationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(lookup.getProject(), data);
long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size();
this.auditService.track(AuditableAction.Tenant_Configuration_Query, "lookup", lookup);
return new QueryResult<>(models, count);
}
@GetMapping("{id}")
@Transactional
public TenantConfiguration get(@PathVariable UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
logger.debug(new MapLogEntry("retrieving" + TenantConfiguration.class.getSimpleName()).And("id", id).And("fields", fieldSet));
this.censorFactory.censor(TenantConfigurationCensor.class).censor(fieldSet);
TenantConfigurationQuery query = this.queryFactory.query(TenantConfigurationQuery.class).ids(id);
TenantConfiguration model = this.builderFactory.builder(TenantConfigurationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.firstAs(fieldSet));
if (model == null)
throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, TenantConfiguration.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.auditService.track(AuditableAction.Tenant_Configuration_Lookup, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", id),
new AbstractMap.SimpleEntry<String, Object>("fields", fieldSet)
));
//this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
return model;
}
@PostMapping("persist/email-client")
@Transactional
@ValidationFilterAnnotation(validator = TenantConfigurationEmailClientPersist.TenantConfigurationEmailClientPersistValidator.ValidatorName, argumentName = "model")
public TenantConfiguration persist(@RequestBody TenantConfigurationEmailClientPersist model, FieldSet fieldSet)
{
logger.debug(new MapLogEntry("persisting").And("type", TenantConfigurationType.EMAIL_CLIENT_CONFIGURATION).And("model", model).And("fields", fieldSet));
TenantConfiguration persisted = this.tenantConfigurationService.persist(model, fieldSet);
this.auditService.track(AuditableAction.Tenant_Configuration_Persist, Map.of(
"type", TenantConfigurationType.EMAIL_CLIENT_CONFIGURATION,
"model", model,
"fields", fieldSet
));
//this._auditService.TrackIdentity(AuditableAction.IdentityTracking_Action);
return persisted;
}
@DeleteMapping("{id}")
@Transactional
public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException {
logger.debug(new MapLogEntry("deleting" + TenantConfiguration.class.getSimpleName()).And("id", id));
this.tenantConfigurationService.deleteAndSave(id);
this.auditService.track(AuditableAction.Tenant_Configuration_Delete, "id", id);
//this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
}
}

View File

@ -0,0 +1,17 @@
//package gr.cite.intelcomp.stiviewer.web.controllers.error;
//
//import gr.cite.tools.exception.MyValidationException;
//import org.springframework.http.HttpStatus;
//import org.springframework.http.ResponseEntity;
//import org.springframework.web.bind.annotation.ControllerAdvice;
//import org.springframework.web.bind.annotation.ExceptionHandler;
//import org.springframework.web.context.request.WebRequest;
//
//@ControllerAdvice
//public class GenericErrorHandler {
//
// @ExceptionHandler(MyValidationException.class)
// public ResponseEntity<MyValidationException> handleValidationException(MyValidationException e, WebRequest webRequest) {
// return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(e);
// }
//}

View File

@ -0,0 +1,101 @@
package gr.cite.notification.web.interceptors;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.notification.common.JsonHandlingService;
import gr.cite.notification.common.lock.LockByKeyManager;
import gr.cite.notification.common.scope.user.UserScope;
import gr.cite.notification.data.UserCredentialEntity;
import gr.cite.notification.model.UserCredential;
import gr.cite.notification.query.UserCredentialQuery;
import gr.cite.tools.data.query.QueryFactory;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import java.util.UUID;
@Component
public class UserInterceptor implements WebRequestInterceptor {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserInterceptor.class));
private final UserScope userScope;
private final ClaimExtractor claimExtractor;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final PlatformTransactionManager transactionManager;
private final UserInterceptorCacheService userInterceptorCacheService;
private final JsonHandlingService jsonHandlingService;
private final QueryFactory queryFactory;
private final LockByKeyManager lockByKeyManager;
@PersistenceContext
public EntityManager entityManager;
@Autowired
public UserInterceptor(
UserScope userScope,
ClaimExtractor claimExtractor,
CurrentPrincipalResolver currentPrincipalResolver,
PlatformTransactionManager transactionManager,
UserInterceptorCacheService userInterceptorCacheService,
JsonHandlingService jsonHandlingService,
QueryFactory queryFactory,
LockByKeyManager lockByKeyManager) {
this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.transactionManager = transactionManager;
this.userInterceptorCacheService = userInterceptorCacheService;
this.jsonHandlingService = jsonHandlingService;
this.queryFactory = queryFactory;
this.lockByKeyManager = lockByKeyManager;
}
@Override
public void preHandle(WebRequest request) {
UUID userId = null;
if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) {
String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal());
if (subjectId == null || subjectId.isBlank()) throw new MyForbiddenException("Empty subjects not allowed");
UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId));
if (cacheValue != null) {
userId = cacheValue.getUserId();
} else {
userId = this.findExistingUserFromDbForce(subjectId);
cacheValue = new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId);
this.userInterceptorCacheService.put(cacheValue);
}
}
this.userScope.setUserId(userId);
}
private UUID findExistingUserFromDbForce(String subjectId){
UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).externalIds(subjectId).firstAs(new BaseFieldSet().ensure(UserCredential._user));
if (userCredential != null) {
return userCredential.getUserId();
} else {
throw new MyForbiddenException("User not created try again.");
}
}
@Override
public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.userScope.setUserId(null);
}
@Override
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
}
}

View File

@ -0,0 +1,10 @@
package gr.cite.notification.web.interceptors;
import gr.cite.tools.cache.CacheOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "cache.user-by-subject-id")
public class UserInterceptorCacheOptions extends CacheOptions {
}

View File

@ -0,0 +1,66 @@
package gr.cite.notification.web.interceptors;
import gr.cite.tools.cache.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.UUID;
@Service
public class UserInterceptorCacheService extends CacheService<UserInterceptorCacheService.UserInterceptorCacheValue> {
public static class UserInterceptorCacheValue {
public UserInterceptorCacheValue() {
}
public UserInterceptorCacheValue(String subjectId, UUID userId) {
this.subjectId = subjectId;
this.userId = userId;
}
private String subjectId;
public String getSubjectId() {
return subjectId;
}
public void setSubjectId(String subjectId) {
this.subjectId = subjectId;
}
private UUID userId;
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
}
@Autowired
public UserInterceptorCacheService(UserInterceptorCacheOptions options) {
super(options);
}
@Override
protected Class<UserInterceptorCacheValue> valueClass() {
return UserInterceptorCacheValue.class;
}
@Override
public String keyOf(UserInterceptorCacheValue value) {
return this.buildKey(value.getSubjectId());
}
public String buildKey(String subject) {
HashMap<String, String> keyParts = new HashMap<>();
keyParts.put("$subject$", subject);
return this.generateKey(keyParts);
}
}

View File

@ -0,0 +1,160 @@
package gr.cite.notification.web.model;
import gr.cite.tools.logging.annotation.LogSensitive;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class Account {
public static class PrincipalInfo {
public static final String _userId = "userId";
public UUID userId;
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
public static final String _subject = "subject";
public UUID subject;
public UUID getSubject() {
return subject;
}
public void setSubject(UUID subject) {
this.subject = subject;
}
public static final String _name = "name";
@LogSensitive
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static final String _scope = "scope";
public List<String> scope;
public List<String> getScope() {
return scope;
}
public void setScope(List<String> scope) {
this.scope = scope;
}
public static final String _client = "client";
public String client;
public String getClient() {
return client;
}
public void setClient(String client) {
this.client = client;
}
public static final String _notBefore = "notBefore";
public Instant notBefore;
public Instant getNotBefore() {
return notBefore;
}
public void setNotBefore(Instant notBefore) {
this.notBefore = notBefore;
}
public static final String _issuedAt = "issuedAt";
public Instant issuedAt;
public Instant getIssuedAt() {
return issuedAt;
}
public void setIssuedAt(Instant issuedAt) {
this.issuedAt = issuedAt;
}
public static final String _authenticatedAt = "authenticatedAt";
public Instant authenticatedAt;
public Instant getAuthenticatedAt() {
return authenticatedAt;
}
public void setAuthenticatedAt(Instant authenticatedAt) {
this.authenticatedAt = authenticatedAt;
}
public static final String _expiresAt = "expiresAt";
public Instant expiresAt;
public Instant getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(Instant expiresAt) {
this.expiresAt = expiresAt;
}
public static final String _more = "more";
@LogSensitive
public Map<String, List<String>> more;
public Map<String, List<String>> getMore() {
return more;
}
public void setMore(Map<String, List<String>> more) {
this.more = more;
}
}
public static final String _isAuthenticated = "isAuthenticated";
private Boolean isAuthenticated;
public Boolean getIsAuthenticated() {
return isAuthenticated;
}
public void setIsAuthenticated(Boolean authenticated) {
isAuthenticated = authenticated;
}
public static final String _principal = "principal";
private PrincipalInfo principal;
public PrincipalInfo getPrincipal() {
return principal;
}
public void setPrincipal(PrincipalInfo principal) {
this.principal = principal;
}
public static final String _permissions = "permissions";
private List<String> permissions;
public List<String> getPermissions() {
return permissions;
}
public void setPermissions(List<String> permissions) {
this.permissions = permissions;
}
}

View File

@ -0,0 +1,83 @@
package gr.cite.notification.web.model;
import gr.cite.commons.web.authz.configuration.AuthorizationConfiguration;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.MyPrincipal;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorKeys;
import gr.cite.notification.common.scope.user.UserScope;
import gr.cite.tools.fieldset.BaseFieldSet;
import gr.cite.tools.fieldset.FieldSet;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class AccountBuilder {
private final ClaimExtractor claimExtractor;
private final Set<String> excludeMoreClaim;
private final AuthorizationConfiguration authorizationConfiguration;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final UserScope userScope;
public AccountBuilder(ClaimExtractor claimExtractor, AuthorizationConfiguration authorizationConfiguration, CurrentPrincipalResolver currentPrincipalResolver, UserScope userScope) {
this.claimExtractor = claimExtractor;
this.authorizationConfiguration = authorizationConfiguration;
this.currentPrincipalResolver = currentPrincipalResolver;
this.userScope = userScope;
this.excludeMoreClaim = Set.of(
ClaimExtractorKeys.Subject,
ClaimExtractorKeys.Name,
ClaimExtractorKeys.Scope,
ClaimExtractorKeys.Client,
ClaimExtractorKeys.IssuedAt,
ClaimExtractorKeys.NotBefore,
ClaimExtractorKeys.AuthenticatedAt,
ClaimExtractorKeys.ExpiresAt);
}
public Account build(FieldSet fields, MyPrincipal principal) {
Account model = new Account();
if (principal == null || !principal.isAuthenticated()) {
model.setIsAuthenticated(false);
return model;
}
model.setIsAuthenticated(true);
FieldSet principalFields = fields.extractPrefixed(BaseFieldSet.asIndexerPrefix(Account._principal));
if (!principalFields.isEmpty()) model.setPrincipal(new Account.PrincipalInfo());
if (principalFields.hasField(Account.PrincipalInfo._subject)) model.getPrincipal().setSubject(this.claimExtractor.subjectUUID(principal));
if (principalFields.hasField(Account.PrincipalInfo._userId)) model.getPrincipal().setUserId(this.userScope.getUserIdSafe());
if (principalFields.hasField(Account.PrincipalInfo._name)) model.getPrincipal().setName(this.claimExtractor.name(principal));
if (principalFields.hasField(Account.PrincipalInfo._scope)) model.getPrincipal().setScope(this.claimExtractor.scope(principal));
if (principalFields.hasField(Account.PrincipalInfo._client)) model.getPrincipal().setClient(this.claimExtractor.client(principal));
if (principalFields.hasField(Account.PrincipalInfo._issuedAt)) model.getPrincipal().setIssuedAt(this.claimExtractor.issuedAt(principal));
if (principalFields.hasField(Account.PrincipalInfo._notBefore)) model.getPrincipal().setNotBefore(this.claimExtractor.notBefore(principal));
if (principalFields.hasField(Account.PrincipalInfo._authenticatedAt)) model.getPrincipal().setAuthenticatedAt(this.claimExtractor.authenticatedAt(principal));
if (principalFields.hasField(Account.PrincipalInfo._expiresAt)) model.getPrincipal().setExpiresAt(this.claimExtractor.expiresAt(principal));
if (principalFields.hasField(Account.PrincipalInfo._more)) {
model.getPrincipal().setMore(new HashMap<>());
for (String key : this.claimExtractor.knownPublicKeys()) {
if (this.excludeMoreClaim.contains(key)) continue;
List<String> values = this.claimExtractor.asStrings(principal, key);
if (values == null || values.size() == 0) continue;
if (!model.getPrincipal().getMore().containsKey(key)) model.getPrincipal().getMore().put(key, new ArrayList<>());
model.getPrincipal().getMore().get(key).addAll(values);
}
}
if (fields.hasField(Account._permissions)) {
List<String> roles = claimExtractor.roles(currentPrincipalResolver.currentPrincipal());
Set<String> permissions = authorizationConfiguration.permissionsOfRoles(roles);
model.setPermissions(new ArrayList<>(permissions));
}
return model;
}
}

View File

@ -0,0 +1,37 @@
package gr.cite.notification.web.model;
import java.util.ArrayList;
import java.util.List;
public class QueryResult<M> {
public QueryResult() { }
public QueryResult(List<M> items, long count)
{
this.items = items;
this.count = count;
}
public List<M> items;
public long count;
public List<M> getItems() {
return items;
}
public void setItems(List<M> items) {
this.items = items;
}
public long getCount() {
return count;
}
public void setCount(long count) {
this.count = count;
}
public static QueryResult<?> Empty()
{
return new QueryResult<>(new ArrayList<>(), 0L);
}
}

View File

@ -0,0 +1,10 @@
package gr.cite.notification.web.scope.tenant;
import gr.cite.tools.cache.CacheOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "cache.tenant-by-code")
public class TenantByCodeCacheOptions extends CacheOptions {
}

View File

@ -0,0 +1,76 @@
package gr.cite.notification.web.scope.tenant;
import gr.cite.notification.convention.ConventionService;
import gr.cite.notification.event.TenantTouchedEvent;
import gr.cite.tools.cache.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.UUID;
@Service
public class TenantByCodeCacheService extends CacheService<TenantByCodeCacheService.TenantByCodeCacheValue> {
public static class TenantByCodeCacheValue {
public TenantByCodeCacheValue() {
}
public TenantByCodeCacheValue(String tenantCode, UUID tenantId) {
this.tenantCode = tenantCode;
this.tenantId = tenantId;
}
private String tenantCode;
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
private UUID tenantId;
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
}
private final ConventionService conventionService;
@Autowired
public TenantByCodeCacheService(TenantByCodeCacheOptions options, ConventionService conventionService) {
super(options);
this.conventionService = conventionService;
}
@EventListener
public void handleTenantTouchedEvent(TenantTouchedEvent event) {
if (!this.conventionService.isNullOrEmpty(event.getTenantCode())) this.evict(this.buildKey(event.getTenantCode()));
if (!this.conventionService.isNullOrEmpty(event.getPreviousTenantCode())) this.evict(this.buildKey(event.getPreviousTenantCode()));
}
@Override
protected Class<TenantByCodeCacheValue> valueClass() {
return TenantByCodeCacheValue.class;
}
@Override
public String keyOf(TenantByCodeCacheValue value) {
return this.buildKey(value.getTenantCode());
}
public String buildKey(String code) {
return this.generateKey(new HashMap<>() {{
put("$code$", code);
}});
}
}

View File

@ -0,0 +1,10 @@
package gr.cite.notification.web.scope.tenant;
import gr.cite.tools.cache.CacheOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "cache.tenant-by-id")
public class TenantByIdCacheOptions extends CacheOptions {
}

View File

@ -0,0 +1,76 @@
package gr.cite.notification.web.scope.tenant;
import gr.cite.notification.convention.ConventionService;
import gr.cite.notification.event.TenantTouchedEvent;
import gr.cite.tools.cache.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Locale;
import java.util.UUID;
@Service
public class TenantByIdCacheService extends CacheService<TenantByIdCacheService.TenantByIdCacheValue> {
public static class TenantByIdCacheValue {
public TenantByIdCacheValue() {
}
public TenantByIdCacheValue(String tenantCode, UUID tenantId) {
this.tenantCode = tenantCode;
this.tenantId = tenantId;
}
private String tenantCode;
public String getTenantCode() {
return tenantCode;
}
public void setTenantCode(String tenantCode) {
this.tenantCode = tenantCode;
}
private UUID tenantId;
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
}
private final ConventionService conventionService;
@Autowired
public TenantByIdCacheService(TenantByIdCacheOptions options, ConventionService conventionService) {
super(options);
this.conventionService = conventionService;
}
@EventListener
public void handleTenantTouchedEvent(TenantTouchedEvent event) {
if (!this.conventionService.isNullOrEmpty(event.getTenantCode())) this.evict(this.buildKey(event.getTenantId()));
}
@Override
protected Class<TenantByIdCacheValue> valueClass() {
return TenantByIdCacheValue.class;
}
@Override
public String keyOf(TenantByIdCacheValue value) {
return this.buildKey(value.getTenantId());
}
public String buildKey(UUID id) {
return this.generateKey(new HashMap<>() {{
put("$tenantId$", id.toString().toLowerCase(Locale.ROOT));
}});
}
}

View File

@ -0,0 +1,166 @@
package gr.cite.notification.web.scope.tenant;
import gr.cite.commons.web.authz.service.AuthorizationService;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.notification.authorization.Permission;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.common.scope.user.UserScope;
import gr.cite.notification.data.TenantUserEntity;
import gr.cite.notification.data.UserEntity;
import gr.cite.notification.data.tenant.TenantScopedBaseEntity;
import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import org.hibernate.Session;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import javax.management.InvalidApplicationException;
import java.util.List;
import java.util.Locale;
@Component
public class TenantInterceptor implements WebRequestInterceptor {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantInterceptor.class));
private final TenantScope tenantScope;
private final UserScope userScope;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final ClaimExtractor claimExtractor;
private final ApplicationContext applicationContext;
private final ErrorThesaurusProperties errorThesaurusProperties;
private final TenantScopeProperties tenantScopeProperties;
private final UserAllowedTenantCacheService userAllowedTenantCacheService;
private final ErrorThesaurusProperties errors;
@PersistenceContext
public EntityManager entityManager;
@Autowired
public TenantInterceptor(
TenantScope tenantScope,
UserScope userScope,
CurrentPrincipalResolver currentPrincipalResolver,
ClaimExtractor claimExtractor,
ApplicationContext applicationContext,
ErrorThesaurusProperties errorThesaurusProperties,
TenantScopeProperties tenantScopeProperties,
UserAllowedTenantCacheService userAllowedTenantCacheService,
ErrorThesaurusProperties errors) {
this.tenantScope = tenantScope;
this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.applicationContext = applicationContext;
this.errorThesaurusProperties = errorThesaurusProperties;
this.tenantScopeProperties = tenantScopeProperties;
this.userAllowedTenantCacheService = userAllowedTenantCacheService;
this.errors = errors;
}
@Override
public void preHandle(WebRequest request) throws InvalidApplicationException {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) return;
boolean isAllowedNoTenant = this.applicationContext.getBean(AuthorizationService.class).authorize(Permission.AllowNoTenant);
if (tenantScope.isSet() && this.entityManager != null) {
List<String> currentPrincipalTenantCodes = this.claimExtractor.asStrings(this.currentPrincipalResolver.currentPrincipal(), TenantScope.TenantCodesClaimName);
if ((currentPrincipalTenantCodes == null || !currentPrincipalTenantCodes.contains(tenantScope.getTenantCode())) && !isAllowedNoTenant) {
logger.warn("tenant not allowed {}", this.tenantScope.getTenant());
//throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage());
}
boolean isUserAllowedTenant = false;
UserAllowedTenantCacheService.UserAllowedTenantCacheValue cacheValue = this.userAllowedTenantCacheService.lookup(this.userAllowedTenantCacheService.buildKey(this.userScope.getUserId(), this.tenantScope.getTenant()));
if (cacheValue != null) {
isUserAllowedTenant = cacheValue.isAllowed();
} else {
isUserAllowedTenant = this.isUserAllowedTenant();
this.userAllowedTenantCacheService.put(new UserAllowedTenantCacheService.UserAllowedTenantCacheValue(this.userScope.getUserId(), this.tenantScope.getTenant(), isUserAllowedTenant));
}
if (isUserAllowedTenant) {
this.entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, tenantScope.getTenant().toString());
} else {
if (isAllowedNoTenant || this.isWhiteListedEndpoint(request)) {
tenantScope.setTenant(null, null);
} else {
logger.warn("tenant not allowed {}", this.tenantScope.getTenant());
//throw new MyForbiddenException(this.errors.getTenantNotAllowed().getCode(), this.errors.getTenantNotAllowed().getMessage());
}
}
} else {
if (!isAllowedNoTenant) {
if (!this.isWhiteListedEndpoint(request)) {
logger.warn("tenant scope not provided");
throw new MyForbiddenException(this.errorThesaurusProperties.getMissingTenant().getCode(), this.errorThesaurusProperties.getMissingTenant().getMessage());
}
}
}
}
private boolean isWhiteListedEndpoint(WebRequest request) {
String servletPath = ((ServletWebRequest) request).getRequest().getServletPath();
if (this.tenantScopeProperties.getWhiteListedEndpoints() != null) {
for (String whiteListedEndpoint : this.tenantScopeProperties.getWhiteListedEndpoints()) {
if (servletPath.toLowerCase(Locale.ROOT).startsWith(whiteListedEndpoint.toLowerCase(Locale.ROOT))) {
return true;
}
}
}
return false;
}
private boolean isUserAllowedTenant() throws InvalidApplicationException {
if (userScope.isSet()) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
Root<UserEntity> root = query.from(UserEntity.class);
Subquery<TenantUserEntity> subQuery = query.subquery(TenantUserEntity.class);
Root<TenantUserEntity> subQueryRoot = subQuery.from(TenantUserEntity.class);
query.where(criteriaBuilder.and(
criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active),
criteriaBuilder.in(root.get(UserEntity._id)).value(subQuery.where(
criteriaBuilder.and(
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._tenantId), this.tenantScope.getTenant()),
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._userId), this.userScope.getUserId()),
criteriaBuilder.equal(subQueryRoot.get(TenantUserEntity._isActive), IsActive.Active)
)).select(subQueryRoot.get(TenantUserEntity._userId)).distinct(true)
)
));
query.multiselect(root.get(UserEntity._id).alias(UserEntity._id));
List<Tuple> results = this.entityManager.createQuery(query).getResultList();
if (results.size() > 0) return true;
}
return false;
}
@Override
public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.tenantScope.setTenant(null, null);
}
@Override
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
}
}

View File

@ -0,0 +1,196 @@
package gr.cite.notification.web.scope.tenant;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.MyPrincipal;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorContext;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.convention.ConventionService;
import gr.cite.notification.data.TenantEntity;
import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import javax.management.InvalidApplicationException;
import java.util.List;
import java.util.UUID;
@Component
public class TenantScopeClaimInterceptor implements WebRequestInterceptor {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScopeClaimInterceptor.class));
private final TenantScope tenantScope;
private final ConventionService conventionService;
private final TenantScopeProperties tenantScopeProperties;
private final ErrorThesaurusProperties errorThesaurusProperties;
private final ClaimExtractor claimExtractor;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final String clientTenantClaimName;
private final ClaimExtractorContext claimExtractorContext;
private final TenantByCodeCacheService tenantByCodeCacheService;
private final TenantByIdCacheService tenantByIdCacheService;
@PersistenceContext
public EntityManager entityManager;
@Autowired
public TenantScopeClaimInterceptor(
TenantScope tenantScope,
ConventionService conventionService,
ClaimExtractor claimExtractor,
CurrentPrincipalResolver currentPrincipalResolver,
ErrorThesaurusProperties errorThesaurusProperties,
TenantScopeProperties tenantScopeProperties,
ClaimExtractorContext claimExtractorContext,
TenantByCodeCacheService tenantByCodeCacheService,
TenantByIdCacheService tenantByIdCacheService
) {
this.tenantScope = tenantScope;
this.conventionService = conventionService;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.errorThesaurusProperties = errorThesaurusProperties;
this.tenantScopeProperties = tenantScopeProperties;
this.claimExtractorContext = claimExtractorContext;
this.tenantByCodeCacheService = tenantByCodeCacheService;
this.tenantByIdCacheService = tenantByIdCacheService;
this.clientTenantClaimName = this.tenantScopeProperties.getClientClaimsPrefix() + TenantScope.TenantClaimName;
}
@Override
public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) return;
MyPrincipal principal = this.currentPrincipalResolver.currentPrincipal();
if (principal != null && principal.isAuthenticated() /* principal.Claims.Any() */) {
Boolean scoped = this.scopeByPrincipal(this.tenantScope, principal);
if (!scoped) scoped = this.scopeByClient(this.tenantScope, principal);
if (!scoped && this.tenantScope.isSet() && this.tenantScopeProperties.getEnforceTrustedTenant()) throw new MyForbiddenException(this.errorThesaurusProperties.getMissingTenant().getCode(), this.errorThesaurusProperties.getMissingTenant().getMessage());
}
}
private Boolean scopeByPrincipal(TenantScope scope, MyPrincipal principal) {
String tenantCode = this.claimExtractor.tenantString(principal);
if (tenantCode == null || tenantCode.isBlank()) tenantCode = this.claimExtractor.asString(principal, this.clientTenantClaimName);
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
if (tenantId == null && tenantCode == null) return false;
if (tenantId == null) {
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));
if (cacheValue != null) {
tenantId = cacheValue.getTenantId();
} else {
tenantId = this.getTenantIdFromDatabase(tenantCode);
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
}
} else {
logger.debug("tenant claim was set to {}", tenantId);
TenantByIdCacheService.TenantByIdCacheValue cacheValue = this.tenantByIdCacheService.lookup(this.tenantByIdCacheService.buildKey(tenantId));
if (cacheValue != null) {
tenantCode = cacheValue.getTenantCode();
} else {
tenantCode = this.getTenantCodeFromDatabase(tenantId);
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
}
}
if (tenantId != null && tenantCode != null && !tenantCode.isBlank()) {
logger.debug("parsed tenant header and set tenant id to {}", tenantId);
this.tenantScope.setTenant(tenantId, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
}
return tenantId != null;
}
private Boolean scopeByClient(TenantScope scope, MyPrincipal principal) throws InvalidApplicationException {
String client = this.claimExtractor.client(principal);
Boolean isWhiteListed = this.tenantScopeProperties.getWhiteListedClients() != null && !this.conventionService.isNullOrEmpty(client) && this.tenantScopeProperties.getWhiteListedClients().contains(client);
logger.debug("client is whitelisted : {}, scope is set: {}, with value {}", isWhiteListed, scope.isSet(), (scope.isSet() ? scope.getTenant() : null));
return isWhiteListed && scope.isSet();
}
private UUID getTenantIdFromDatabase(String tenantCode) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where(
criteriaBuilder.and(
criteriaBuilder.equal(root.get(TenantEntity._code), tenantCode),
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
)
).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id));
List<Tuple> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
Object o;
try {
o = results.get(0).get(TenantEntity._id);
} catch (IllegalArgumentException e) {
return null;
}
if (o == null) return null;
try {
return (UUID) o;
} catch (ClassCastException e) {
return null;
}
}
return null;
}
private String getTenantCodeFromDatabase(UUID tenantId) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where(
criteriaBuilder.and(
criteriaBuilder.equal(root.get(TenantEntity._id), tenantId),
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
)
).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code));
List<Tuple> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
Object o;
try {
o = results.get(0).get(TenantEntity._code);
} catch (IllegalArgumentException e) {
return null;
}
if (o == null) return null;
try {
return (String) o;
} catch (ClassCastException e) {
return null;
}
}
return null;
}
@Override
public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.tenantScope.setTenant(null, null);
this.claimExtractorContext.removeReplaceParameter(TenantScope.TenantReplaceParameter);
}
@Override
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
}
}

View File

@ -0,0 +1,9 @@
package gr.cite.notification.web.scope.tenant;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(TenantScopeProperties.class)
public class TenantScopeConfiguration {
}

View File

@ -0,0 +1,161 @@
package gr.cite.notification.web.scope.tenant;
import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractorContext;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.convention.ConventionService;
import gr.cite.notification.data.TenantEntity;
import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
import javax.management.InvalidApplicationException;
import java.util.List;
import java.util.UUID;
@Component
public class TenantScopeHeaderInterceptor implements WebRequestInterceptor {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScopeHeaderInterceptor.class));
private final TenantScope tenantScope;
private final ConventionService conventionService;
private final TenantByCodeCacheService tenantByCodeCacheService;
private final TenantByIdCacheService tenantByIdCacheService;
private final ClaimExtractorContext claimExtractorContext;
private final CurrentPrincipalResolver currentPrincipalResolver;
@PersistenceContext
public EntityManager entityManager;
@Autowired
public TenantScopeHeaderInterceptor(
TenantScope tenantScope,
ConventionService conventionService,
TenantByCodeCacheService tenantByCodeCacheService,
TenantByIdCacheService tenantByIdCacheService,
ClaimExtractorContext claimExtractorContext,
CurrentPrincipalResolver currentPrincipalResolver
) {
this.tenantScope = tenantScope;
this.conventionService = conventionService;
this.tenantByCodeCacheService = tenantByCodeCacheService;
this.tenantByIdCacheService = tenantByIdCacheService;
this.claimExtractorContext = claimExtractorContext;
this.currentPrincipalResolver = currentPrincipalResolver;
}
@Override
public void preHandle(WebRequest request) throws InvalidApplicationException {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) return;
String tenantCode = request.getHeader(TenantScope.TenantClaimName);
logger.debug("retrieved request tenant header is: {header}", tenantCode);
if (this.conventionService.isNullOrEmpty(tenantCode)) return;
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
if (tenantId == null && tenantCode == null) return;
if (tenantId == null) {
TenantByCodeCacheService.TenantByCodeCacheValue cacheValue = this.tenantByCodeCacheService.lookup(this.tenantByCodeCacheService.buildKey(tenantCode));
if (cacheValue != null) {
tenantId = cacheValue.getTenantId();
} else {
tenantId = this.getTenantIdFromDatabase(tenantCode);
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
}
} else {
TenantByIdCacheService.TenantByIdCacheValue cacheValue = this.tenantByIdCacheService.lookup(this.tenantByIdCacheService.buildKey(tenantId));
if (cacheValue != null) {
tenantCode = cacheValue.getTenantCode();
} else {
tenantCode = this.getTenantCodeFromDatabase(tenantId);
this.tenantByCodeCacheService.put(new TenantByCodeCacheService.TenantByCodeCacheValue(tenantCode, tenantId));
this.tenantByIdCacheService.put(new TenantByIdCacheService.TenantByIdCacheValue(tenantCode, tenantId));
}
}
if (tenantId != null && tenantCode != null && !tenantCode.isBlank()) {
logger.debug("parsed tenant header and set tenant id to {tenant}", tenantId);
this.tenantScope.setTenant(tenantId, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
}
}
private UUID getTenantIdFromDatabase(String tenantCode) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where(
criteriaBuilder.and(
criteriaBuilder.equal(root.get(TenantEntity._code), tenantCode),
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
)
).multiselect(root.get(TenantEntity._id).alias(TenantEntity._id));
List<Tuple> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
Object o;
try {
o = results.get(0).get(TenantEntity._id);
} catch (IllegalArgumentException e) {
return null;
}
if (o == null) return null;
try {
return UUID.class.cast(o);
} catch (ClassCastException e) {
return null;
}
}
return null;
}
private String getTenantCodeFromDatabase(UUID tenantId) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
Root<TenantEntity> root = query.from(TenantEntity.class);
query = query.where(
criteriaBuilder.and(
criteriaBuilder.equal(root.get(TenantEntity._id), tenantId),
criteriaBuilder.equal(root.get(TenantEntity._isActive), IsActive.Active)
)
).multiselect(root.get(TenantEntity._code).alias(TenantEntity._code));
List<Tuple> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
Object o;
try {
o = results.get(0).get(TenantEntity._code);
} catch (IllegalArgumentException e) {
return null;
}
if (o == null) return null;
try {
return String.class.cast(o);
} catch (ClassCastException e) {
return null;
}
}
return null;
}
@Override
public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.tenantScope.setTenant(null, null);
this.claimExtractorContext.removeReplaceParameter(TenantScope.TenantReplaceParameter);
}
@Override
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
}
}

View File

@ -0,0 +1,43 @@
package gr.cite.notification.web.scope.tenant;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashSet;
import java.util.List;
@ConfigurationProperties(prefix = "tenant.interceptor")
public class TenantScopeProperties {
private String clientClaimsPrefix;
public String getClientClaimsPrefix() {
return clientClaimsPrefix;
}
public void setClientClaimsPrefix(String clientClaimsPrefix) {
this.clientClaimsPrefix = clientClaimsPrefix;
}
private HashSet<String> whiteListedClients;
public HashSet<String> getWhiteListedClients() {
return whiteListedClients;
}
public void setWhiteListedClients(HashSet<String> whiteListedClients) {
this.whiteListedClients = whiteListedClients;
}
private List<String> whiteListedEndpoints;
public List<String> getWhiteListedEndpoints() {
return whiteListedEndpoints;
}
public void setWhiteListedEndpoints(List<String> whiteListedEndpoints) {
this.whiteListedEndpoints = whiteListedEndpoints;
}
private Boolean enforceTrustedTenant;
public Boolean getEnforceTrustedTenant() {
return enforceTrustedTenant;
}
public void setEnforceTrustedTenant(Boolean enforceTrustedTenant) {
this.enforceTrustedTenant = enforceTrustedTenant;
}
}

View File

@ -0,0 +1,10 @@
package gr.cite.notification.web.scope.tenant;
import gr.cite.tools.cache.CacheOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "cache.user-allowed-tenant")
public class UserAllowedTenantCacheOptions extends CacheOptions {
}

View File

@ -0,0 +1,90 @@
package gr.cite.notification.web.scope.tenant;
import gr.cite.notification.event.UserAddedToTenantEvent;
import gr.cite.notification.event.UserRemovedFromTenantEvent;
import gr.cite.tools.cache.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Locale;
import java.util.UUID;
@Service
public class UserAllowedTenantCacheService extends CacheService<UserAllowedTenantCacheService.UserAllowedTenantCacheValue> {
public static class UserAllowedTenantCacheValue {
public UserAllowedTenantCacheValue() {
}
public UserAllowedTenantCacheValue(UUID userId, UUID tenantId, boolean isAllowed) {
this.userId = userId;
this.tenantId = tenantId;
this.isAllowed = isAllowed;
}
private UUID userId;
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
private UUID tenantId;
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
private boolean isAllowed;
public boolean isAllowed() {
return isAllowed;
}
public void setAllowed(boolean allowed) {
isAllowed = allowed;
}
}
@Autowired
public UserAllowedTenantCacheService(UserAllowedTenantCacheOptions options) {
super(options);
}
@EventListener
public void handleUserRemovedFromTenantEvent(UserRemovedFromTenantEvent event) {
this.evict(this.buildKey(event.getUserId(), event.getTenantId()));
}
@EventListener
public void handleUserAddedToTenantEvent(UserAddedToTenantEvent event) {
this.evict(this.buildKey(event.getUserId(), event.getTenantId()));
}
@Override
protected Class<UserAllowedTenantCacheValue> valueClass() {
return UserAllowedTenantCacheValue.class;
}
@Override
public String keyOf(UserAllowedTenantCacheValue value) {
return this.buildKey(value.getUserId(), value.getTenantId());
}
public String buildKey(UUID userId, UUID tenantId) {
return this.generateKey(new HashMap<>() {{
put("$user_id$", userId.toString().toLowerCase(Locale.ROOT));
put("$tenant_id$", tenantId.toString().toLowerCase(Locale.ROOT));
}});
}
}

View File

@ -0,0 +1,154 @@
//package gr.cite.notification.web.scope.user;
//
//
//import gr.cite.commons.web.oidc.principal.CurrentPrincipalResolver;
//import gr.cite.commons.web.oidc.principal.extractor.ClaimExtractor;
//import gr.cite.notification.common.enums.IsActive;
//import gr.cite.notification.common.scope.user.UserScope;
//import gr.cite.notification.data.UserEntity;
//import gr.cite.notification.locale.LocaleService;
//import gr.cite.tools.logging.LoggerService;
//import org.slf4j.LoggerFactory;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.lang.NonNull;
//import org.springframework.stereotype.Component;
//import org.springframework.transaction.PlatformTransactionManager;
//import org.springframework.transaction.TransactionDefinition;
//import org.springframework.transaction.TransactionStatus;
//import org.springframework.transaction.support.DefaultTransactionDefinition;
//import org.springframework.ui.ModelMap;
//import org.springframework.web.context.request.WebRequest;
//import org.springframework.web.context.request.WebRequestInterceptor;
//
//import javax.management.InvalidApplicationException;
//import jakarta.persistence.EntityManager;
//import jakarta.persistence.PersistenceContext;
//import jakarta.persistence.Tuple;
//import jakarta.persistence.criteria.CriteriaBuilder;
//import jakarta.persistence.criteria.CriteriaQuery;
//import jakarta.persistence.criteria.Root;
//import java.time.Instant;
//import java.util.List;
//import java.util.UUID;
//
//@Component
//public class UserInterceptor implements WebRequestInterceptor {
// private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserInterceptor.class));
// private final UserScope userScope;
// private final ClaimExtractor claimExtractor;
// private final CurrentPrincipalResolver currentPrincipalResolver;
// private final LocaleService localeService;
// private final PlatformTransactionManager transactionManager;
// private final UserInterceptorCacheService userInterceptorCacheService;
// @PersistenceContext
// public EntityManager entityManager;
//
// @Autowired
// public UserInterceptor(
// UserScope userScope,
// LocaleService localeService,
// ClaimExtractor claimExtractor,
// CurrentPrincipalResolver currentPrincipalResolver,
// PlatformTransactionManager transactionManager,
// UserInterceptorCacheService userInterceptorCacheService
// ) {
// this.userScope = userScope;
// this.localeService = localeService;
// this.currentPrincipalResolver = currentPrincipalResolver;
// this.claimExtractor = claimExtractor;
// this.transactionManager = transactionManager;
// this.userInterceptorCacheService = userInterceptorCacheService;
// }
//
// @Override
// public void preHandle(WebRequest request) throws InvalidApplicationException {
// UUID userId = null;
// if (this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) {
// String subjectId = this.claimExtractor.subjectString(this.currentPrincipalResolver.currentPrincipal());
//
// UserInterceptorCacheService.UserInterceptorCacheValue cacheValue = this.userInterceptorCacheService.lookup(this.userInterceptorCacheService.buildKey(subjectId));
// if (cacheValue != null) {
// userId = cacheValue.getUserId();
// } else {
// userId = this.getUserIdFromDatabase(subjectId);
// if (userId == null) userId = this.createUser(subjectId);
//
// this.userInterceptorCacheService.put(new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId));
// }
// }
// this.userScope.setUserId(userId);
// }
//
// private UUID getUserIdFromDatabase(String subjectId) throws InvalidApplicationException {
// CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
// CriteriaQuery<Tuple> query = criteriaBuilder.createQuery(Tuple.class);
// Root<UserEntity> root = query.from(UserEntity.class);
// query.where(
// criteriaBuilder.and(
//// criteriaBuilder.equal(root.get(UserEntity._subjectId), subjectId),
// criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active)
// ));
//
// query.multiselect(root.get(UserEntity._id).alias(UserEntity._id));
//
// List<Tuple> results = this.entityManager.createQuery(query).getResultList();
// if (results.size() == 1) {
// Object o;
// try {
// o = results.get(0).get(UserEntity._id);
// } catch (IllegalArgumentException e) {
// return null;
// }
// if (o == null) return null;
// try {
// return UUID.class.cast(o);
// } catch (ClassCastException e) {
// return null;
// }
// }
// return null;
// }
//
// private UUID createUser(String subjectId) {
// String name = this.claimExtractor.name(this.currentPrincipalResolver.currentPrincipal());
// String familyName = this.claimExtractor.familyName(this.currentPrincipalResolver.currentPrincipal());
// if (name == null) name = subjectId;
// UserEntity user = new UserEntity();
// user.setId(UUID.randomUUID());
// user.setCreatedAt(Instant.now());
// user.setUpdatedAt(Instant.now());
// user.setName(name);
//// user.setLastName(familyName == null ? name : familyName);
// user.setIsActive(IsActive.Active);
//// user.setSubjectId(subjectId);
//// user.setCulture(this.localeService.cultureName());
//// user.setTimezone(this.localeService.timezoneName());
//// user.setLanguage(this.localeService.language());
//
// DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
// definition.setName(UUID.randomUUID().toString());
// definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
// definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// TransactionStatus status = null;
// try {
// status = transactionManager.getTransaction(definition);
// this.entityManager.persist(user);
//
// this.entityManager.flush();
// transactionManager.commit(status);
// } catch (Exception ex) {
// if (status != null) transactionManager.rollback(status);
// throw ex;
// }
// return user.getId();
// }
//
// @Override
// public void postHandle(@NonNull WebRequest request, ModelMap model) {
// this.userScope.setUserId(null);
// }
//
// @Override
// public void afterCompletion(@NonNull WebRequest request, Exception ex) {
// }
//}

View File

@ -0,0 +1,10 @@
//package gr.cite.notification.web.scope.user;
//
//import gr.cite.tools.cache.CacheOptions;
//import org.springframework.boot.context.properties.ConfigurationProperties;
//import org.springframework.context.annotation.Configuration;
//
//@Configuration
//@ConfigurationProperties(prefix = "cache.user-by-subject-id")
//public class UserInterceptorCacheOptions extends CacheOptions {
//}

View File

@ -0,0 +1,77 @@
//package gr.cite.notification.web.scope.user;
//
//import gr.cite.notification.convention.ConventionService;
//import gr.cite.notification.event.UserTouchedEvent;
//import gr.cite.tools.cache.CacheService;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.context.event.EventListener;
//import org.springframework.stereotype.Service;
//
//import java.util.HashMap;
//import java.util.UUID;
//
//@Service
//public class UserInterceptorCacheService extends CacheService<UserInterceptorCacheService.UserInterceptorCacheValue> {
//
// public static class UserInterceptorCacheValue {
//
// public UserInterceptorCacheValue() {
// }
//
// public UserInterceptorCacheValue(String subjectId, UUID userId) {
// this.subjectId = subjectId;
// this.userId = userId;
// }
//
// private String subjectId;
//
// public String getSubjectId() {
// return subjectId;
// }
//
// public void setSubjectId(String subjectId) {
// this.subjectId = subjectId;
// }
//
// private UUID userId;
//
// public UUID getUserId() {
// return userId;
// }
//
// public void setUserId(UUID userId) {
// this.userId = userId;
// }
// }
//
// private final ConventionService conventionService;
//
// @Autowired
// public UserInterceptorCacheService(UserInterceptorCacheOptions options, ConventionService conventionService) {
// super(options);
// this.conventionService = conventionService;
// }
//
// @EventListener
// public void handleUserTouchedEvent(UserTouchedEvent event) {
// if (!this.conventionService.isNullOrEmpty(event.getSubjectId())) this.evict(this.buildKey(event.getSubjectId()));
// if (!this.conventionService.isNullOrEmpty(event.getPreviousSubjectId())) this.evict(this.buildKey(event.getPreviousSubjectId()));
// }
//
// @Override
// protected Class<UserInterceptorCacheValue> valueClass() {
// return UserInterceptorCacheValue.class;
// }
//
// @Override
// public String keyOf(UserInterceptorCacheValue value) {
// return this.buildKey(value.getSubjectId());
// }
//
//
// public String buildKey(String subject) {
// return this.generateKey(new HashMap<>() {{
// put("$subject$", subject);
// }});
// }
//}

View File

@ -0,0 +1,20 @@
spring:
jackson:
default-property-inclusion: non_null
config:
import: optional:classpath:config/app.env[.properties], optional:file:../config/app.env[.properties],
optional:classpath:config/db.yml[.yml], optional:classpath:config/db-${spring.profiles.active}.yml[.yml], optional:file:../config/db-${spring.profiles.active}.yml[.yml],
optional:classpath:config/permissions.yml[.yml], optional:classpath:config/permissions-${spring.profiles.active}.yml[.yml], optional:file:../config/permissions-${spring.profiles.active}.yml[.yml],
optional:classpath:config/errors.yml[.yml], optional:classpath:config/errors-${spring.profiles.active}.yml[.yml], optional:file:../config/errors-${spring.profiles.active}.yml[.yml],
optional:classpath:config/security.yml[.yml], optional:classpath:config/security-${spring.profiles.active}.yml[.yml], optional:file:../config/security-${spring.profiles.active}.yml[.yml],
optional:classpath:config/server.yml[.yml], optional:classpath:config/server-${spring.profiles.active}.yml[.yml], optional:file:../config/server-${spring.profiles.active}.yml[.yml],
optional:classpath:config/logging.yml[.yml], optional:classpath:config/logging-${spring.profiles.active}.yml[.yml], optional:file:../config/logging-${spring.profiles.active}.yml[.yml],
optional:classpath:config/idpclaims.yml[.yml], optional:classpath:config/idpclaims-${spring.profiles.active}.yml[.yml], optional:file:../config/idpclaims-${spring.profiles.active}.yml[.yml],
optional:classpath:config/cache.yml[.yml], optional:classpath:config/cache-${spring.profiles.active}.yml[.yml], optional:file:../config/cache-${spring.profiles.active}.yml[.yml],
optional:classpath:config/tenant.yml[.yml], optional:classpath:config/tenant-${spring.profiles.active}.yml[.yml], optional:file:../config/tenant-${spring.profiles.active}.yml[.yml],
optional:classpath:config/locale.yml[.yml], optional:classpath:config/locale-${spring.profiles.active}.yml[.yml], optional:file:../config/locale-${spring.profiles.active}.yml[.yml],
optional:classpath:config/cors.yml[.yml], optional:classpath:config/cors-${spring.profiles.active}.yml[.yml], optional:file:../config/cors-${spring.profiles.active}.yml[.yml],
optional:classpath:config/email.yml[.yml], optional:classpath:config/email-${spring.profiles.active}.yml[.yml], optional:file:../config/email-${spring.profiles.active}.yml[.yml],
optional:classpath:config/queue.yml[.yml], optional:classpath:config/queue-${spring.profiles.active}.yml[.yml], optional:file:../config/queue-${spring.profiles.active}.yml[.yml],
optional:classpath:config/cipher.yml[.yml], optional:classpath:config/cipher-${spring.profiles.active}.yml[.yml], optional:file:../config/cipher-${spring.profiles.active}.yml[.yml],
optional:classpath:config/formatting.yml[.yml], optional:classpath:config/formatting-${spring.profiles.active}.yml[.yml], optional:file:../config/formatting-${spring.profiles.active}.yml[.yml]

View File

@ -0,0 +1,77 @@
cache:
manager:
fallbackToNoOpCache: true
caffeineCaches:
- names: [ apikey ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteMinutes: 10
expireAfterAccessMinutes: 10
refreshAfterWriteMinutes: 10
- names: [ tenantByCode ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteMinutes: 10
expireAfterAccessMinutes: 10
refreshAfterWriteMinutes: 10
- names: [ tenantById ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteMinutes: 10
expireAfterAccessMinutes: 10
refreshAfterWriteMinutes: 10
- names: [ userBySubjectId ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteMinutes: 5
expireAfterAccessMinutes: 5
refreshAfterWriteMinutes: 5
- names: [ userAccessTenant ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteMinutes: 5
expireAfterAccessMinutes: 5
refreshAfterWriteMinutes: 5
- names: [ formattingUserProfile ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteMinutes: 1
expireAfterAccessMinutes: 1
refreshAfterWriteMinutes: 1
mapCaches:
- names: [ cacheB ]
allowNullValues: true
storeByValue: true
apiKey:
name: apikey
keyPattern: resolve_$keyhash$:v0
userBySubjectId:
name: userBySubjectId
keyPattern: user_by_subject_$subject$:v0
tenantByCode:
name: tenantByCode
keyPattern: tenant_by_code_$code$:v0
tenantById:
name: tenantById
keyPattern: tenant_by_id_$tenantId$:v0
userAllowedTenant:
name: userAccessTenant
keyPattern: user_access_tenant_$user_id$_$tenant_id$:v0
formattingUserProfileCache:
name: formattingUserProfile
keyPattern: formatting_user_profile$user_id$:v0
template:
name: template
key-pattern: ------

View File

@ -0,0 +1,35 @@
cipher-profiles:
profile-map:
configuration-profile-name: "configuration"
queue-profile-name: "queue"
notification-profile-name: "queue"
cipher:
# salted-hash:
# default-o: null
# options: null
symetric-encryption:
default-o: null
options:
configuration:
aes:
key: ${CIPHER_SYMETRIC_ENCRYPTION_CONFIGURATION_AES_KEY:}
iv: ${CIPHER_SYMETRIC_ENCRYPTION_CONFIGURATION_AES_IV:}
queue:
aes:
key: ${CIPHER_SYMETRIC_ENCRYPTION_QUEUE_AES_KEY:}
iv: ${CIPHER_SYMETRIC_ENCRYPTION_QUEUE_AES_IV:}
masking:
default: null
options:
configuration:
character: "*"
clear-begining: 2
clear-ending: 4
at-least-percentage: 70
digital-signature:
default: null
options:
configuration:
certificate-path: null
certificate-password: null

View File

@ -0,0 +1,3 @@
web:
cors:
allowed-origins: [ http://localhost, http://localhost:4200 ]

View File

@ -0,0 +1,7 @@
web:
cors:
enabled: true
allowed-methods: [ HEAD, GET, POST, PUT, DELETE, PATCH ]
allowed-headers: [ Authorization, Cache-Control, Content-Type, Content-Disposition, x-tenant ]
exposed-headers: [ Authorization, Cache-Control, Content-Type, Content-Disposition ]
allow-credentials: false

View File

@ -0,0 +1,7 @@
spring:
jpa:
show-sql: true
properties:
hibernate:
show_sql: true
format_sql: false

View File

@ -0,0 +1,24 @@
spring:
jpa:
properties:
hibernate:
ddl-auto: validate
dialect: org.hibernate.dialect.PostgreSQLDialect
hibernate:
naming:
physical-strategy: gr.cite.notification.config.db.PrefixPhysicalNamingStrategy
implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
datasource:
url: ${DB_CONNECTION_STRING}
username: ${DB_USER}
password: ${DB_PASSWORD}
driver-class-name: org.postgresql.Driver
hikari:
connection-timeout: 30000
minimum-idle: 3
maximum-pool-size: 5
idle-timeout: 600000
max-lifetime: 1800000
naming-strategy:
prefix: ant_

View File

@ -0,0 +1,14 @@
spring:
mail:
host: ${MAIL_HOST:}
port: ${MAIL_PORT:}
username: ${MAIL_USERNAME:}
password: ${MAIL_PASSWORD:}
properties:
mail:
smtp:
auth: ${MAIL_AUTH:}
starttls:
enable: ${MAIL_TLS:}
email:
address: ${MAIL_ADDRESS:}

View File

@ -0,0 +1,43 @@
error-thesaurus:
hash-conflict:
code: 100
message: there is a hash conflict for the item modified. please reload to get the latest changes
forbidden:
code: 101
message: insufficient rights
system-error:
code: 102
message: an unexpected system error occurred
missing-tenant:
code: 103
message: tenant scope not provided
invalid-api-key:
code: 104
message: provided APIKey not valid
stale-api-key:
code: 105
message: there was a problem authorizing you with your API key. Please try again. Contact the system administrator if the problem persists
model-validation:
code: 106
message: validation error
sensitive-info:
code: 107
message: you are attempting to access sensitive information. please don't do that
non-person-principal:
code: 108
message: the operation is available only to person users
blocking-consent:
code: 113
message: user consents are not sufficient to complete the operation
single-tenant-configuration-per-type-supported:
code: 116
message: a single tenant configuration entry per config type is supported
incompatible-tenant-configuration-types:
code: 117
message: the provided tenant configuration type is incompatible
missing-totp-token:
code: 118
message: totp token not provided
overlapping-tenant-configuration-notifier-list:
code: 119
message: Overlapping Tenant Configuration Notifier List

View File

@ -0,0 +1,6 @@
formatting:
options:
integer-format: "%,d"
decimal-digits-round: 2
decimal-format: "#0.00"
date-time-format: "YYYY-MM-dd'T'HH:mm:ss"

View File

@ -0,0 +1,39 @@
idpclient:
claims:
mapping:
Subject:
- type: sub
Name:
- type: name
Client:
- type: client_id
AuthenticationMethod:
- type: amr
NotBefore:
- type: nbf
AuthenticatedAt:
- type: auth_time
ExpiresAt:
- type: exp
Email:
- type: email
Roles:
- type: resource_access
path: dmp_web.roles
Scope:
- type: scope
AccessToken:
- type: x-access-token
visibility: SENSITIVE
IssuedAt:
- type: iat
Issuer:
- type: iss
Audience:
- type: aud
TokenType:
- type: typ
AuthorizedParty:
- type: azp
Authorities:
- type: authorities

View File

@ -0,0 +1,4 @@
locale:
timezone: UTC
language: en
culture: en-US

View File

@ -0,0 +1,2 @@
logging:
config: classpath:logging/logback-dev.xml

View File

@ -0,0 +1,35 @@
logging:
context:
request:
requestIdKey: req.id
requestRemoteHostKey: req.remoteHost
requestUriKey: req.requestURI
requestQueryStringKey: req.queryString
requestUrlKey : req.requestURL
requestMethodKey: req.method
requestUserAgentKey: req.userAgent
requestForwardedForKey: req.xForwardedFor
requestSchemeKey: req.scheme
requestRemoteAddressKey: req.remoteAddr
requestRemotePortKey: req.remotePort
requestRemoteUserKey: req.remoteUser
principal:
subjectKey: usr.subject
nameKey: usr.name
clientKey: usr.client
audit:
enable: true
requestRemoteHostKey: req.remoteHost
requestUriKey: req.requestURI
requestQueryStringKey: req.queryString
requestUrlKey : req.requestURL
requestMethodKey: req.method
requestUserAgentKey: req.userAgent
requestForwardedForKey: req.xForwardedFor
requestSchemeKey: req.scheme
requestRemoteAddressKey: req.remoteAddr
requestRemotePortKey: req.remotePort
requestRemoteUserKey: req.remoteUser
principalSubjectKey: usr.subject
principalNameKey: usr.name
principalClientKey: usr.client

View File

@ -0,0 +1,182 @@
permissions:
extendedClaims: [ ]
policies:
# Tenants
BrowseTenant:
roles:
- ic-sti-superuser
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EditTenant:
roles:
- ic-sti-superuser
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
DeleteTenant:
roles:
- ic-sti-superuser
claims: [ ]
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
AllowNoTenant:
roles:
- ic-sti-superuser
claims: [ ]
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# Users
BrowseUser:
roles:
- ic-sti-superuser
- tenantadmin
clients: [ ]
allowAnonymous: true
allowAuthenticated: false
EditUser:
roles:
- admin
- ic-sti-superuser
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
DeleteUser:
roles:
- ic-sti-superuser
claims: [ ]
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# UserContactInfo
BrowseUserContactInfo:
roles:
- ic-sti-superuser
clients: [ ]
allowAnonymous: true
allowAuthenticated: false
EditUserContactInfo:
roles:
- ic-sti-superuser
- user
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
DeleteUserContactInfo:
roles:
- ic-sti-superuser
claims: [ ]
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
#Notification
BrowseNotification:
roles:
- Admin
clients: [ ]
allowAnonymous: true
allowAuthenticated: false
EditNotification:
roles:
- Admin
clients: [ ]
allowAnonymous: true
allowAuthenticated: false
DeleteNotification:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
#Tenant Configuration
BrowseTenantConfiguration:
roles:
- ic-sti-superuser
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EditTenantConfiguration:
roles:
- ic-sti-superuser
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
#User Notification Preference
BrowseUserNotificationPreference:
roles:
- ic-sti-superuser
clients: [ ]
allowAnonymous: true
allowAuthenticated: false
EditUserNotificationPreference:
roles:
- ic-sti-superuser
- user
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# ViewPage Permissions
ViewNotificationPage:
roles:
- ic-sti-superuser
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
ViewNotificationEventRulePage:
roles:
- ic-sti-superuser
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
ViewInAppNotificationPage:
roles:
- ic-sti-superuser
- tenantadmin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
ViewNotificationTemplatePage:
roles:
- ic-sti-superuser
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# Notification Template Permissions
BrowseNotificationTemplate:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EditNotificationTemplate:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
DeleteNotificationTemplate:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# In App Notification Permissions
BrowseInAppNotification:
roles:
- Admin
- User
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
DeleteInAppNotification:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false

View File

@ -0,0 +1,21 @@
queue:
rabbitmq:
enable: true
durable: true
queue: cite_dmp_devel_notification_inbox_queue
exchange: cite_dmp_devel_queue
listenerEnabled: true
publisherEnabled: true
task:
publisher:
enable: true
options:
exchange: cite_dmp_devel_queue
rabbitmq:
enable: true
listener:
enable: true
options:
exchange: cite_dmp_devel_queue
rabbitmq:
enable: true

View File

@ -0,0 +1,56 @@
spring:
rabbitmq:
host: ${RABBIT_HOST}
port: ${RABBIT_PORT}
username: ${RABBIT_USER}
password: ${RABBIT_PASS}
ssl:
enabled: false
queue:
rabbitmq:
enable: false
app-id: ${QUEUE_APP_ID}
durable: null
queue: null
exchange: null
listenerEnabled: true
publisherEnabled: true
#TODO
connection-recovery:
enable: true
network-recovery-interval: 5000
unreachable-recovery-interval: 5000
task:
publisher:
enable: false
options:
exchange: null
forget-me-completed-topic: forgetme.completed
what-you-know-about-me-completed-topic: whatyouknowaboutme.completed
generate-file-topic: generate.file
rabbitmq:
enable: false
interval-seconds: 30
options:
retry-threashold: 100
retry-delay-step-seconds: 300
max-retry-delay-seconds: 10800
too-old-to-send-seconds: 604800
confirm-timeout-seconds: 30
listener:
enable: false
options:
exchange: null
notify-topic: notification.notify
tenant-removal-topic: tenant.remove
tenant-touched-topic: tenant.touch
user-removal-topic: user.remove
user-touched-topic: user.touch
rabbitmq:
enable: false
interval-seconds: 30
options:
retry-threashold: 100
retry-delay-step-seconds: 300
max-retry-delay-seconds: 10800
too-old-to-send-seconds: 604800

View File

@ -0,0 +1,20 @@
web:
security:
enabled: true
authorized-endpoints: [ api ]
allowed-endpoints: [ public, dataset, master-item, test ]
idp:
api-key:
enabled: true
authorization-header: Authorization
client-id: ${IDP_APIKEY_CLIENT_ID}
client-secret: ${IDP_APIKEY_CLIENT_SECRET}
scope: ${IDP_APIKEY_SCOPE}
resource:
token-type: JWT
opaque:
client-id: ${IDP_OPAQUE_CLIENT_ID}
client-secret: ${IDP_OPAQUE_CLIENT_SECRET}
jwt:
claims: [ role, x-role ]
issuer-uri: ${IDP_ISSUER_URI:}

View File

@ -0,0 +1,2 @@
server:
forward-headers-strategy: FRAMEWORK

View File

@ -0,0 +1,3 @@
server:
port: ${WEB_PORT}
forward-headers-strategy: NONE

View File

@ -0,0 +1,8 @@
tenant:
multitenancy:
is-multitenant: false
interceptor:
client-claims-prefix: client_
white-listed-clients: [ ]
enforce-trusted-tenant: false
white-listed-endpoints: [ '/api/principal/my-tenants', '/api/principal/me','/api/user/user-settings', '/error', '/api/tenant-request' ]

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%date{ISO8601} [%thread] %-5level %logger{36} [%X{req.id}] - %message%n</Pattern>
</encoder>
</appender>
<appender name="TROUBLESHOOTING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/logging.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/logging.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%date{ISO8601} [%thread] %-5level %logger{36} [%X{req.id}] - %message%n</Pattern>
</encoder>
</appender>
<appender name="AUDITING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/auditing.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/auditing.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%date{ISO8601} - %X{req.id} - %message%n</Pattern>
</encoder>
</appender>
<logger name="org.springframework.web" level="INFO" additivity="false">
<appender-ref ref="TROUBLESHOOTING"/>
<appender-ref ref="STDOUT"/>
</logger>
<logger name="org.hibernate" level="INFO" additivity="false">
<appender-ref ref="TROUBLESHOOTING"/>
<appender-ref ref="STDOUT"/>
</logger>
<logger name="gr.cite" level="DEBUG" additivity="false">
<appender-ref ref="TROUBLESHOOTING"/>
<appender-ref ref="STDOUT"/>
</logger>
<logger name="org.springframework.data.elasticsearch.client.WIRE" level="TRACE" additivity="false">
<appender-ref ref="TROUBLESHOOTING"/>
<appender-ref ref="STDOUT"/>
</logger>
<logger name="audit" level="INFO" additivity="false">
<appender-ref ref="AUDITING"/>
<appender-ref ref="STDOUT"/>
</logger>
<root level="info">
<appender-ref ref="TROUBLESHOOTING"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>

View File

@ -0,0 +1,10 @@
validation.empty=Value cannot be empty
validation.hashempty=Hash must be set
validation.lowerthanmin=Value must be larger than {value}
validation.largerthanmax=Value must be less than {value}
validation.invalidid=Not valid id
General_ItemNotFound=Item {0} of type {1} not found
Validation_Required={0} is required
Validation_OverPosting=Too much info
Validation_MaxLength={0} too long
Validation_UnexpectedValue=Unexpected value in field {0}

View File

@ -0,0 +1,6 @@
validation.empty=el-Value cannot be empty
validation.hashempty=el-Hash must be set
validation.lowerthanmin=el-Value must be larger than {value}
validation.largerthanmax=el-Value must be less than {value}
validation.invalidid=el-Not valid id
General_ItemNotFound=el-Item {0} of type {1} not found

View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>gr.cite</groupId>
<artifactId>annotation-service-parent</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>annotation</artifactId>
<properties>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</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-mail</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>data-tools</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>field-set</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>oidc-authn</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>logging</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>oidc-authz</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>exceptions</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>validation</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>cipher</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,48 @@
package gr.cite.notification.audit;
import gr.cite.tools.logging.EventId;
public class AuditableAction {
public static final EventId Tenant_Available_Notifiers_Query = new EventId(2006, "Tenant_Available_Notifiers_Query");
public static final EventId Principal_Lookup = new EventId(6000, "Principal_Lookup");
public static final EventId Tenants_Lookup = new EventId(6001, "Tenants_Lookup");
public static final EventId User_Available_Notifiers_Query = new EventId(10004, "User_Available_Notifiers_Query");
public static final EventId User_Query = new EventId(11000, "User_Query");
public static final EventId User_Lookup = new EventId(11001, "User_Lookup");
public static final EventId User_Persist = new EventId(11002, "User_Persist");
public static final EventId User_Delete = new EventId(11003, "User_Delete");
public static final EventId Tenant_Query = new EventId(12000, "Tenant_Query");
public static final EventId Tenant_Lookup = new EventId(12001, "Tenant_Lookup");
public static final EventId Tenant_Persist = new EventId(12002, "Tenant_Persist");
public static final EventId Tenant_Delete = new EventId(12003, "Tenant_Delete");
public static final EventId Notification_Query = new EventId(19000, "Notification_Query");
public static final EventId Notification_Lookup = new EventId(19001, "Notification_Lookup");
public static final EventId Notification_Persist = new EventId(19002, "Notification_Persist");
public static final EventId Notification_Delete = new EventId(19003, "Notification_Delete");
public static final EventId InApp_Notification_Query = new EventId(20000, "InApp_Notification_Query");
public static final EventId InApp_Notification_Lookup = new EventId(20001, "InApp_Notification_Lookup");
public static final EventId InApp_Notification_Persist = new EventId(20002, "InApp_Notification_Persist");
public static final EventId InApp_Notification_Delete = new EventId(20003, "InApp_Notification_Delete");
public static final EventId InApp_Notification_Read = new EventId(20003, "InApp_Notification_Read");
public static final EventId InApp_Notification_Read_All = new EventId(20003, "InApp_Notification_Read_All");
public static final EventId Tenant_Configuration_Query = new EventId(21000, "Tenant_Configuration_Query");
public static final EventId Tenant_Configuration_Lookup = new EventId(21001, "Tenant_Configuration_Lookup");
public static final EventId Tenant_Configuration_Persist = new EventId(21002, "Tenant_Configuration_Persist");
public static final EventId Tenant_Configuration_Delete = new EventId(21003, "Tenant_Configuration_Delete");
public static final EventId User_Notification_Preference_Query = new EventId(22000, "User_Notification_Preference_Query");
public static final EventId User_Notification_Preference_Lookup = new EventId(22001, "User_Notification_Preference_Lookup");
public static final EventId User_Notification_Preference_Persist = new EventId(22002, "User_Notification_Preference_Persist");
public static final EventId User_Notification_Preference_Delete = new EventId(22003, "User_Notification_Preference_Delete");
public static final EventId Notification_Template_Query = new EventId(23000, "Notification_Template_Query");
public static final EventId Notification_Template_Lookup = new EventId(23001, "Notification_Template_Lookup");
public static final EventId Notification_Template_Persist = new EventId(23002, "Notification_Template_Persist");
public static final EventId Notification_Template_Delete = new EventId(23003, "Notification_Template_Delete");
}

View File

@ -0,0 +1,8 @@
package gr.cite.notification.authorization;
import java.util.EnumSet;
public enum AuthorizationFlags {
None, Permission, Owner;
public static final EnumSet<AuthorizationFlags> OwnerOrPermission = EnumSet.of(Owner, Permission);
}

View File

@ -0,0 +1,6 @@
package gr.cite.notification.authorization;
import gr.cite.commons.web.authz.policy.AuthorizationRequirement;
public class OwnedAuthorizationRequirement implements AuthorizationRequirement {
}

View File

@ -0,0 +1,26 @@
package gr.cite.notification.authorization;
import gr.cite.commons.web.authz.policy.AuthorizationResource;
import java.util.List;
import java.util.UUID;
public class OwnedResource extends AuthorizationResource {
private List<UUID> userIds;
public OwnedResource(UUID userId) {
this(List.of(userId));
}
public OwnedResource(List<UUID> userIds) {
this.userIds = userIds;
}
public List<UUID> getUserIds() {
return userIds;
}
public void setUserIds(List<UUID> userIds) {
this.userIds = userIds;
}
}

View File

@ -0,0 +1,48 @@
package gr.cite.notification.authorization;
public final class Permission {
//User
public static String BrowseUser = "BrowseUser";
public static String EditUser = "EditUser";
public static String DeleteUser = "DeleteUser";
//UserContactInfo
public static String BrowseUserContactInfo = "BrowseUserContactInfo";
public static String EditUserContactInfo = "EditUserContactInfo";
public static String DeleteUserContactInfo = "DeleteUserContactInfo";
//Tenant
public static String BrowseTenant = "BrowseTenant";
public static String EditTenant = "EditTenant";
public static String DeleteTenant = "DeleteTenant";
public static String AllowNoTenant = "AllowNoTenant";
//Notification
public static final String BrowseNotification = "BrowseNotification";
public static String EditNotification = "EditNotification";
public static String DeleteNotification = "DeleteNotification";
public static final String BrowseTenantConfiguration = "BrowseTenantConfiguration";
public static final String EditTenantConfiguration = "EditTenantConfiguration";
//Notification Preference
public static final String BrowseUserNotificationPreference = "BrowseUserNotificationPreference";
public static final String EditUserNotificationPreference = "EditUserNotificationPreference";
//Notification Template
public static final String BrowseNotificationTemplate = "BrowseNotificationTemplate";
public static String EditNotificationTemplate = "EditNotificationTemplate";
public static String DeleteNotificationTemplate = "DeleteNotificationTemplate";
//InApp Notification
public static final String BrowseInAppNotification = "BrowseInAppNotification";
public static String DeleteInAppNotification = "DeleteInAppNotification";
// UI Pages
public static String ViewTenantConfigurationPage = "ViewTenantConfigurationPage";
public static String ViewNotificationPage = "ViewNotificationPage";
public static String ViewNotificationEventRulePage = "ViewNotificationEventRulePage";
public static String ViewInAppNotificationPage = "ViewInAppNotificationPage";
public static String ViewNotificationTemplatePage = "ViewNotificationTemplatePage";
}

View File

@ -0,0 +1,93 @@
package gr.cite.notification.cache;
import gr.cite.notification.config.formatting.FormattingUserprofileCacheOptions;
import gr.cite.notification.event.UserTouchedEvent;
import gr.cite.tools.cache.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Locale;
import java.util.UUID;
@Service
public class FormattingUserprofileCacheCacheService extends CacheService<FormattingUserprofileCacheCacheService.UserFormattingProfileCacheValue> {
public static class UserFormattingProfileCacheValue {
public UserFormattingProfileCacheValue() {
}
public UserFormattingProfileCacheValue(UUID userId, String zone, String culture, String language) {
this.userId = userId;
this.zone = zone;
this.culture = culture;
this.language = language;
}
private UUID userId;
private String zone;
private String culture;
private String language;
public String getZone() {
return zone;
}
public void setZone(String zone) {
this.zone = zone;
}
public String getCulture() {
return culture;
}
public void setCulture(String culture) {
this.culture = culture;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
}
@Autowired
public FormattingUserprofileCacheCacheService(FormattingUserprofileCacheOptions options) {
super(options);
}
@EventListener
public void handleUserTouchedEvent(UserTouchedEvent event) {
if (event.getUserId() != null) this.evict(this.buildKey(event.getUserId()));
}
@Override
protected Class<UserFormattingProfileCacheValue> valueClass() {
return UserFormattingProfileCacheValue.class;
}
@Override
public String keyOf(UserFormattingProfileCacheValue value) {
return this.buildKey(value.getUserId());
}
public String buildKey(UUID userId) {
return this.generateKey(new HashMap<>() {{
put("$user_id$", userId.toString().toLowerCase(Locale.ROOT));
}});
}
}

View File

@ -0,0 +1,43 @@
package gr.cite.notification.common;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import gr.cite.tools.logging.LoggerService;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class JsonHandlingService {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(JsonHandlingService.class));
private final ObjectMapper objectMapper = new ObjectMapper();
public String toJson(Object item) throws JsonProcessingException {
if (item == null) return null;
return objectMapper.writeValueAsString(item);
}
public String toJsonSafe(Object item) {
if (item == null) return null;
try {
return objectMapper.writeValueAsString(item);
} catch (Exception ex) {
logger.error("Json Parsing Error: " + ex.getLocalizedMessage(), ex);
return null;
}
}
public <T> T fromJson(Class<T> type, String json) throws JsonProcessingException {
if (json == null) return null;
return objectMapper.readValue(json, type);
}
public <T> T fromJsonSafe(Class<T> type, String json) {
if (json == null) return null;
try {
return objectMapper.readValue(json, type);
} catch (Exception ex) {
logger.error("Json Parsing Error: " + ex.getLocalizedMessage(), ex);
return null;
}
}
}

View File

@ -0,0 +1,8 @@
package gr.cite.notification.common;
public class StringUtils {
public static Boolean isNullOrEmpty(String string) {
return string == null || string.isEmpty();
}
}

View File

@ -0,0 +1,117 @@
package gr.cite.notification.common;
import com.fasterxml.jackson.core.JsonProcessingException;
import gr.cite.notification.common.types.xml.XmlSerializable;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.management.InvalidApplicationException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class XmlHandlingService {
public String generateXml(Document doc) throws TransformerException {
TransformerFactory tFact = TransformerFactory.newInstance();
Transformer trans = tFact.newTransformer();
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
DOMSource source = new DOMSource(doc);
trans.setOutputProperty(OutputKeys.INDENT, "yes");
trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
trans.transform(source, result);
return writer.toString();
}
public String toXml(Object item) throws JsonProcessingException, JAXBException, ParserConfigurationException, InvalidApplicationException, TransformerException {
if (XmlSerializable.class.isAssignableFrom(item.getClass())){
Document document = this.getDocument();
if (document == null) throw new InvalidApplicationException("Can not create document");
document.appendChild(((XmlSerializable)item).toXml(document));
return this.generateXml(document);
}
JAXBContext context = JAXBContext.newInstance(item.getClass());
Marshaller marshaller = context.createMarshaller();
StringWriter out = new StringWriter();
marshaller.marshal(item, out);
return out.toString();
}
public String toXmlSafe(Object item) {
if (item == null) return null;
try {
return this.toXml(item);
} catch (Exception ex) {
return null;
}
}
public <T> T fromXml(Class<T> type, String xmlString) throws JAXBException, InstantiationException, IllegalAccessException, ParserConfigurationException, IOException, SAXException {
if (XmlSerializable.class.isAssignableFrom(type)){
XmlSerializable<T> object = (XmlSerializable<T>)type.newInstance();
return (T) object.fromXml(this.getDocument(xmlString).getDocumentElement());
} else {
JAXBContext jaxbContext = JAXBContext.newInstance(type);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
return (T) jaxbUnmarshaller.unmarshal(new StringReader(xmlString));
}
}
public <T> T fromXmlSafe(Class<T> type, String xmlString) {
if (xmlString == null) return null;
try {
return this.fromXml(type, xmlString);
} catch (Exception ex) {
return null;
}
}
// public <T extends XmlSerializable<T>> T xmlSerializableFromXml(Class<T> type, String xmlString) throws JAXBException, InstantiationException, IllegalAccessException, ParserConfigurationException, IOException, SAXException {
// T object = type.newInstance();
// return (T) object.fromXml(this.getDocument(xmlString).getDocumentElement());
// }
//
// public <T extends XmlSerializable<T>> T xmlSerializableFromXmlSafe(Class<T> type, String xmlString) {
// if (xmlString == null) return null;
// try {
// return this.xmlSerializableFromXml(type, xmlString);
// } catch (Exception ex) {
// return null;
// }
// }
public Document getDocument(String xml) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
InputSource inputStream = new InputSource(new StringReader(xml));
return docBuilder.parse(inputStream);
}
public Document getDocument() throws ParserConfigurationException {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
return docBuilder.newDocument();
}
}

View File

@ -0,0 +1,29 @@
package gr.cite.notification.common.enums;
import com.fasterxml.jackson.annotation.JsonValue;
import gr.cite.notification.data.conventers.DatabaseEnum;
import java.util.Map;
public enum ContactInfoType implements DatabaseEnum<Short> {
Email((short) 0),
MobilePhone((short) 1),
LandLinePhone((short) 2);
private final Short value;
ContactInfoType(Short value) {
this.value = value;
}
@Override
@JsonValue
public Short getValue() {
return value;
}
private static final Map<Short, ContactInfoType> map = EnumUtils.getEnumValueMap(ContactInfoType.class);
public static ContactInfoType of(Short i) {
return map.get(i);
}
}

View File

@ -0,0 +1,16 @@
package gr.cite.notification.common.enums;
import gr.cite.notification.data.conventers.DatabaseEnum;
import java.util.HashMap;
import java.util.Map;
public class EnumUtils {
public static <EnumType extends Enum<EnumType> & DatabaseEnum<EnumValue>, EnumValue> Map<EnumValue, EnumType> getEnumValueMap(Class<EnumType> enumType){
HashMap<EnumValue, EnumType> map = new HashMap<>();
for (EnumType v : enumType.getEnumConstants()) {
map.put(v.getValue(), v);
}
return map;
}
}

View File

@ -0,0 +1,28 @@
package gr.cite.notification.common.enums;
import com.fasterxml.jackson.annotation.JsonValue;
import gr.cite.notification.data.conventers.DatabaseEnum;
import java.util.Map;
public enum IsActive implements DatabaseEnum<Short> {
Inactive((short) 0),
Active((short) 1);
private final Short value;
IsActive(Short value) {
this.value = value;
}
@JsonValue
public Short getValue() {
return value;
}
private static final Map<Short, IsActive> map = EnumUtils.getEnumValueMap(IsActive.class);
public static IsActive of(Short i) {
return map.get(i);
}
}

View File

@ -0,0 +1,29 @@
package gr.cite.notification.common.enums;
import com.fasterxml.jackson.annotation.JsonValue;
import gr.cite.notification.data.conventers.DatabaseEnum;
import java.util.Map;
public enum TenantConfigurationType implements DatabaseEnum<Short> {
EMAIL_CLIENT_CONFIGURATION((short)1),
DEFAULT_USER_LOCALE((short)3),
NOTIFIER_LIST((short)4);
private final Short value;
TenantConfigurationType(Short value) {
this.value = value;
}
@JsonValue
public Short getValue() {
return value;
}
private static final Map<Short, TenantConfigurationType> map = EnumUtils.getEnumValueMap(TenantConfigurationType.class);
public static TenantConfigurationType of(Short i) {
return map.get(i);
}
}

View File

@ -0,0 +1,58 @@
package gr.cite.notification.common.lock;
import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class LockByKeyManager {
private static class LockWrapper {
private final ReentrantLock lock = new ReentrantLock();
private final AtomicInteger numberOfThreadsInQueue = new AtomicInteger(1);
private LockWrapper addThreadInQueue() {
numberOfThreadsInQueue.incrementAndGet();
return this;
}
private int removeThreadFromQueue() {
return numberOfThreadsInQueue.decrementAndGet();
}
}
private static ConcurrentHashMap<String, LockWrapper> locks = new ConcurrentHashMap<String, LockWrapper>();
public void lock(String key) {
LockWrapper lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue());
lockWrapper.lock.lock();
}
public boolean tryLock(String key, long timeout, TimeUnit unit) throws InterruptedException {
LockWrapper lockWrapper = null;
try {
lockWrapper = locks.compute(key, (k, v) -> v == null ? new LockWrapper() : v.addThreadInQueue());
return lockWrapper.lock.tryLock(timeout, unit);
} catch (Exception ex){
if (lockWrapper != null && lockWrapper.removeThreadFromQueue() == 0) {
// NB : We pass in the specific value to remove to handle the case where another thread would queue right before the removal
locks.remove(key, lockWrapper);
}
throw ex;
}
}
public void unlock(String key) {
LockWrapper lockWrapper = locks.get(key);
lockWrapper.lock.unlock();
if (lockWrapper.removeThreadFromQueue() == 0) {
// NB : We pass in the specific value to remove to handle the case where another thread would queue right before the removal
locks.remove(key, lockWrapper);
}
}
}

View File

@ -0,0 +1,125 @@
package gr.cite.notification.common.scope.fake;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class FakeRequestAttributes implements RequestAttributes {
private Map<String, Object> requestAttributeMap = new HashMap<>();
private final Map<String, Runnable> requestDestructionCallbacks = new LinkedHashMap<>(8);
private volatile boolean requestActive = true;
@Override
public Object getAttribute(String name, int scope) {
if (scope == RequestAttributes.SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException("Cannot ask for request attribute - request is not active anymore!");
}
return this.requestAttributeMap.get(name);
} else {
throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName());
}
}
@Override
public void setAttribute(String name, Object value, int scope) {
if (scope == RequestAttributes.SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException("Cannot set request attribute - request is not active anymore!");
}
this.requestAttributeMap.put(name, value);
} else {
throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName());
}
}
@Override
public void removeAttribute(String name, int scope) {
if (scope == RequestAttributes.SCOPE_REQUEST) {
if (isRequestActive()) {
removeRequestDestructionCallback(name);
this.requestAttributeMap.remove(name);
}
} else {
throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName());
}
}
@Override
public String[] getAttributeNames(int scope) {
if (scope == RequestAttributes.SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException("Cannot ask for request attributes - request is not active anymore!");
}
return this.requestAttributeMap.keySet().toArray(new String[0]);
} else {
throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName());
}
//return new String[0];
}
@Override
public void registerDestructionCallback(String name, Runnable callback, int scope) {
if (scope == SCOPE_REQUEST) {
registerRequestDestructionCallback(name, callback);
} else {
throw new IllegalStateException("Only " + RequestAttributes.SCOPE_REQUEST + " allowed for " + FakeRequestAttributes.class.getSimpleName());
}
}
protected final void registerRequestDestructionCallback(String name, Runnable callback) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(callback, "Callback must not be null");
synchronized (this.requestDestructionCallbacks) {
this.requestDestructionCallbacks.put(name, callback);
}
}
@Override
public Object resolveReference(String key) {
// Not supported
return null;
}
@Override
public String getSessionId() {
return null;
}
@Override
public Object getSessionMutex() {
return null;
}
public void requestCompleted() {
executeRequestDestructionCallbacks();
for (String name : getAttributeNames(RequestAttributes.SCOPE_REQUEST)) {
this.removeAttribute(name, RequestAttributes.SCOPE_REQUEST);
}
this.requestActive = false;
}
private final boolean isRequestActive() {
return this.requestActive;
}
private final void removeRequestDestructionCallback(String name) {
Assert.notNull(name, "Name must not be null");
synchronized (this.requestDestructionCallbacks) {
this.requestDestructionCallbacks.remove(name);
}
}
private void executeRequestDestructionCallbacks() {
synchronized (this.requestDestructionCallbacks) {
for (Runnable runnable : this.requestDestructionCallbacks.values()) {
runnable.run();
}
this.requestDestructionCallbacks.clear();
}
}
}

View File

@ -0,0 +1,40 @@
package gr.cite.notification.common.scope.fake;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.io.Closeable;
public class FakeRequestScope implements Closeable {
private RequestAttributes initialRequestAttributes = null;
private FakeRequestAttributes currentRequestAttributes = null;
boolean isInUse = false;
public FakeRequestScope() {
this.reset();
}
public void reset() {
this.close();
this.isInUse = true;
this.initialRequestAttributes = RequestContextHolder.getRequestAttributes();
this.currentRequestAttributes = new FakeRequestAttributes();
RequestContextHolder.setRequestAttributes(this.currentRequestAttributes);
}
@Override
public void close() {
if (!this.isInUse) return;
this.isInUse = false;
if (initialRequestAttributes != null) RequestContextHolder.setRequestAttributes(initialRequestAttributes);
else RequestContextHolder.resetRequestAttributes();
if (currentRequestAttributes != null) currentRequestAttributes.requestCompleted();
this.initialRequestAttributes = null;
this.currentRequestAttributes = null;
}
}

View File

@ -0,0 +1,9 @@
package gr.cite.notification.common.scope.tenant;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(MultitenancyProperties.class)
public class MultitenancyConfiguration {
}

View File

@ -0,0 +1,16 @@
package gr.cite.notification.common.scope.tenant;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "tenant.multitenancy")
public class MultitenancyProperties {
private boolean isMultitenant;
public boolean isMultitenant() {
return isMultitenant;
}
public void setIsMultitenant(boolean multitenant) {
isMultitenant = multitenant;
}
}

View File

@ -0,0 +1,86 @@
package gr.cite.notification.common.scope.tenant;
import gr.cite.notification.data.tenant.TenantScopedBaseEntity;
import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import org.hibernate.Session;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
import javax.management.InvalidApplicationException;
import java.util.UUID;
@Component
@RequestScope
public class TenantScope {
public static final String TenantReplaceParameter = "::TenantCode::";
public static final String TenantCodesClaimName = "TenantCodes";
public static final String TenantClaimName = "x-tenant";
private MultitenancyProperties multitenancy;
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(TenantScope.class));
private UUID tenant = null;
private String tenantCode = null;
private UUID initialTenant = null;
private String initialTenantCode = null;
@Autowired
public TenantScope(MultitenancyProperties multitenancy) {
this.multitenancy = multitenancy;
}
public Boolean isMultitenant() {
return multitenancy.isMultitenant();
}
public Boolean isSet() {
if (!this.isMultitenant()) return true;
return this.tenant != null;
}
public UUID getTenant() throws InvalidApplicationException {
if (!this.isMultitenant()) return null;
if (this.tenant == null) throw new InvalidApplicationException("tenant not set");
return this.tenant;
}
public String getTenantCode() throws InvalidApplicationException {
if (!this.isMultitenant()) return null;
if (this.tenant == null) throw new InvalidApplicationException("tenant not set");
return this.tenantCode;
}
public void setTempTenant(EntityManager entityManager, UUID tenant, String tenantCode) {
this.tenant = tenant;
if(this.tenant != null) {
entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, this.tenant.toString());
}
}
public void removeTempTenant(EntityManager entityManager) {
this.tenant = this.initialTenant;
this.tenantCode = this.initialTenantCode;
if(this.initialTenant != null) {
entityManager
.unwrap(Session.class)
.enableFilter(TenantScopedBaseEntity.tenantFilter).setParameter(TenantScopedBaseEntity.tenantFilterTenantParam, this.initialTenant.toString());
}
}
public void setTenant(UUID tenant, String tenantCode) {
if (this.isMultitenant()) {
this.tenant = tenant;
this.initialTenant = tenant;
this.tenantCode = tenantCode;
this.initialTenantCode = tenantCode;
}
}
}

View File

@ -0,0 +1,8 @@
package gr.cite.notification.common.scope.tenant;
import java.util.UUID;
public interface TenantScoped {
void setTenantId(UUID tenantId);
UUID getTenantId();
}

View File

@ -0,0 +1,34 @@
package gr.cite.notification.common.scope.user;
import gr.cite.tools.logging.LoggerService;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
import javax.management.InvalidApplicationException;
import java.util.UUID;
@Component
@RequestScope
public class UserScope {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserScope.class));
private UUID userId = null;
public Boolean isSet(){
return this.userId != null;
}
public UUID getUserId() throws InvalidApplicationException {
if (this.userId == null) throw new InvalidApplicationException("user not set");
return this.userId;
}
public UUID getUserIdSafe() {
return this.userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
}

View File

@ -0,0 +1,36 @@
package gr.cite.notification.common.types.notification;
public class Attachment {
private String fileRef, fileName, mimeType;
public Attachment(String fileRef, String fileName, String mimeType) {
this.fileRef = fileRef;
this.fileName = fileName;
this.mimeType = mimeType;
}
public String getFileRef() {
return fileRef;
}
public void setFileRef(String fileRef) {
this.fileRef = fileRef;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
}

View File

@ -0,0 +1,32 @@
package gr.cite.notification.common.types.notification;
import gr.cite.notification.common.enums.ContactInfoType;
public class ContactPair {
private ContactInfoType type;
private String contact;
public ContactPair(ContactInfoType type, String contact) {
this.type = type;
this.contact = contact;
}
public ContactPair() {
}
public ContactInfoType getType() {
return type;
}
public void setType(ContactInfoType type) {
this.type = type;
}
public String getContact() {
return contact;
}
public void setContact(String contact) {
this.contact = contact;
}
}

View File

@ -0,0 +1,35 @@
package gr.cite.notification.common.types.tenant;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "config")
@XmlAccessorType(XmlAccessType.FIELD)
public class TenantConfigEntity {
@XmlElement(name = "deposit-configuration")
private TenantDepositConfigEntity deposit;
@XmlElement(name = "file-transformers-configuration")
private TenantFileTransformersConfigEntity fileTransformers;
public TenantDepositConfigEntity getDeposit() {
return deposit;
}
public void setDeposit(TenantDepositConfigEntity deposit) {
this.deposit = deposit;
}
public TenantFileTransformersConfigEntity getFileTransformers() {
return fileTransformers;
}
public void setFileTransformers(TenantFileTransformersConfigEntity fileTransformers) {
this.fileTransformers = fileTransformers;
}
}

View File

@ -0,0 +1,25 @@
package gr.cite.notification.common.types.tenant;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import java.util.List;
@XmlAccessorType(XmlAccessType.FIELD)
public class TenantDepositConfigEntity {
@XmlElementWrapper(name = "sources")
@XmlElement(name = "source")
private List<TenantSourceEntity> sources;
public List<TenantSourceEntity> getSources() {
return sources;
}
public void setSources(List<TenantSourceEntity> sources) {
this.sources = sources;
}
}

View File

@ -0,0 +1,24 @@
package gr.cite.notification.common.types.tenant;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import java.util.List;
@XmlAccessorType(XmlAccessType.FIELD)
public class TenantFileTransformersConfigEntity {
@XmlElementWrapper(name = "sources")
@XmlElement(name = "source")
private List<TenantSourceEntity> sources;
public List<TenantSourceEntity> getSources() {
return sources;
}
public void setSources(List<TenantSourceEntity> sources) {
this.sources = sources;
}
}

View File

@ -0,0 +1,79 @@
package gr.cite.notification.common.types.tenant;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import java.util.List;
@XmlAccessorType(XmlAccessType.FIELD)
public class TenantSourceEntity {
@XmlElement(name = "url")
private String url;
@XmlElementWrapper(name = "codes")
@XmlElement(name = "code")
private List<String> codes;
@XmlElement(name = "issuer-url")
private String issuerUrl;
@XmlElement(name = "client-id")
private String clientId;
@XmlElement(name = "client-secret")
private String clientSecret;
@XmlElement(name = "scope")
private String scope;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public List<String> getCodes() {
return codes;
}
public void setCodes(List<String> codes) {
this.codes = codes;
}
public String getIssuerUrl() {
return issuerUrl;
}
public void setIssuerUrl(String issuerUrl) {
this.issuerUrl = issuerUrl;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}

View File

@ -0,0 +1,37 @@
package gr.cite.notification.common.types.tenantconfiguration;
public class DefaultUserLocaleConfigurationDataContainer {
public static class Field {
public static final String LANGUAGE = "language";
public static final String TIME_ZONE = "timeZone";
public static final String CULTURE = "culture";
}
private String language;
private String timeZone;
private String culture;
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getTimeZone() {
return timeZone;
}
public void setTimeZone(String timeZone) {
this.timeZone = timeZone;
}
public String getCulture() {
return culture;
}
public void setCulture(String culture) {
this.culture = culture;
}
}

View File

@ -0,0 +1,87 @@
package gr.cite.notification.common.types.tenantconfiguration;
public class EmailClientConfigurationDataContainer {
public static class Field {
public static final String REQUIRE_CREDENTIALS = "requireCredentials";
public static final String ENABLE_SSL = "enableSSL";
public static final String CERTIFICATE_PATH = "certificatePath";
public static final String HOST_SERVER = "hostServer";
public static final String HOST_PORT_NO = "hostPortNo";
public static final String EMAIL_ADDRESS = "emailAddress";
public static final String EMAIL_USER_NAME = "emailUserName";
public static final String EMAIL_PASSWORD = "emailPassword";
}
private Boolean requireCredentials;
private Boolean enableSSL;
private String certificatePath;
private String hostServer;
private Integer hostPortNo;
private String emailAddress;
private String emailUserName;
private String emailPassword;
public Boolean getRequireCredentials() {
return requireCredentials;
}
public void setRequireCredentials(Boolean requireCredentials) {
this.requireCredentials = requireCredentials;
}
public Boolean getEnableSSL() {
return enableSSL;
}
public void setEnableSSL(Boolean enableSSL) {
this.enableSSL = enableSSL;
}
public String getCertificatePath() {
return certificatePath;
}
public void setCertificatePath(String certificatePath) {
this.certificatePath = certificatePath;
}
public String getHostServer() {
return hostServer;
}
public void setHostServer(String hostServer) {
this.hostServer = hostServer;
}
public Integer getHostPortNo() {
return hostPortNo;
}
public void setHostPortNo(Integer hostPortNo) {
this.hostPortNo = hostPortNo;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getEmailUserName() {
return emailUserName;
}
public void setEmailUserName(String emailUserName) {
this.emailUserName = emailUserName;
}
public String getEmailPassword() {
return emailPassword;
}
public void setEmailPassword(String emailPassword) {
this.emailPassword = emailPassword;
}
}

View File

@ -0,0 +1,60 @@
package gr.cite.notification.common.types.user;
import java.util.UUID;
public class AdditionalInfoEntity {
private String avatarUrl;
private String timezone;
private String culture;
private String language;
private String roleOrganization;
private UUID organizationId;
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
public String getTimezone() {
return timezone;
}
public void setTimezone(String timezone) {
this.timezone = timezone;
}
public String getCulture() {
return culture;
}
public void setCulture(String culture) {
this.culture = culture;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public UUID getOrganizationId() {
return organizationId;
}
public void setOrganizationId(UUID organizationId) {
this.organizationId = organizationId;
}
public String getRoleOrganization() {
return roleOrganization;
}
public void setRoleOrganization(String roleOrganization) {
this.roleOrganization = roleOrganization;
}
}

View File

@ -0,0 +1,87 @@
package gr.cite.notification.common.types.xml;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
public class XmlBuilder {
private static final Logger logger = LoggerFactory.getLogger(XmlBuilder.class);
public static Document getDocument() {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder;
try {
docBuilder = docFactory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
return doc;
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
logger.error(e.getMessage(), e);
return null;
}
}
public static String generateXml(Document doc) {
TransformerFactory tFact = TransformerFactory.newInstance();
Transformer trans;
try {
trans = tFact.newTransformer();
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
DOMSource source = new DOMSource(doc);
trans.setOutputProperty(OutputKeys.INDENT, "yes");
trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
trans.transform(source, result);
return writer.toString();
} catch (TransformerException e) {
// TODO Auto-generated catch block
logger.error(e.getMessage(), e);
return null;
}
}
public static Document fromXml(String xml) {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder;
try {
docBuilder = docFactory.newDocumentBuilder();
InputSource inputStream = new InputSource(new StringReader(xml));
Document doc = docBuilder.parse(inputStream);
return doc;
} catch (ParserConfigurationException | SAXException | IOException e) {
// TODO Auto-generated catch block
logger.error(e.getMessage(), e);
return null;
}
}
public static Element getNodeFromListByTagName(NodeList list, String tagName) {
for (int temp = 0; temp < list.getLength(); temp++) {
Node element = list.item(temp);
if (element.getNodeType() == Node.ELEMENT_NODE) {
if (element.getNodeName().equals(tagName)) return (Element) element;
}
}
return null;
}
}

View File

@ -0,0 +1,11 @@
package gr.cite.notification.common.types.xml;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public interface XmlSerializable<T> {
Element toXml(Document doc);
T fromXml(Element item);
}

View File

@ -0,0 +1,63 @@
package gr.cite.notification.common.validation;
import gr.cite.notification.convention.ConventionService;
import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.tools.exception.MyValidationException;
import gr.cite.tools.validation.AbstractValidator;
import gr.cite.tools.validation.ValidationResult;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public abstract class BaseValidator<T> extends AbstractValidator<T> {
protected final ConventionService conventionService;
protected final ErrorThesaurusProperties errors;
protected BaseValidator(ConventionService conventionService, ErrorThesaurusProperties errors) {
this.conventionService = conventionService;
this.errors = errors;
}
@Override
public void validateForce(Object target) {
this.validate(target);
ValidationResult result = result();
if (!result.isValid()) {
List<Map.Entry<String, List<String>>> errorsMap = this.flattenValidationResult();
throw new MyValidationException(this.errors.getModelValidation().getCode(), errorsMap);
}
}
protected Boolean isValidGuid(UUID guid) {
return this.conventionService.isValidGuid(guid);
}
protected Boolean isValidHash(String hash) {
return this.conventionService.isValidHash(hash);
}
protected Boolean isEmpty(String value) {
return this.conventionService.isNullOrEmpty(value);
}
protected Boolean isListNullOrEmpty(List<?> value) {
return this.conventionService.isListNullOrEmpty(value);
}
protected Boolean isNull(Object value) {
return value == null;
}
protected Boolean isNull(Collection<?> value) {
return value == null;
}
protected Boolean lessEqualLength(String value, int size) {
return value.length() <= size;
}
protected Boolean lessEqual(Integer value, int target) {
return value <= target;
}
}

View File

@ -0,0 +1,20 @@
package gr.cite.notification.config.db;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
@ConfigurationProperties(prefix = "naming-strategy")
public class NamingStrategyProperties {
private final String prefix;
@ConstructorBinding
public NamingStrategyProperties(String prefix) {
this.prefix = prefix;
}
public String getPrefix() {
return prefix;
}
}

View File

@ -0,0 +1,28 @@
package gr.cite.notification.config.db;
import gr.cite.notification.common.StringUtils;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@EnableConfigurationProperties({NamingStrategyProperties.class})
public class PrefixPhysicalNamingStrategy extends PhysicalNamingStrategyStandardImpl {
private final NamingStrategyProperties namingStrategyProperties;
public PrefixPhysicalNamingStrategy(NamingStrategyProperties namingStrategyProperties) {
this.namingStrategyProperties = namingStrategyProperties;
}
@Override
public Identifier toPhysicalTableName(Identifier logicalName, JdbcEnvironment context) {
if (StringUtils.isNullOrEmpty(namingStrategyProperties.getPrefix()))
return super.toPhysicalTableName(logicalName, context);
Identifier identifier = new Identifier(namingStrategyProperties.getPrefix() + logicalName.getText(), logicalName.isQuoted());
return super.toPhysicalTableName(identifier, context);
}
}

View File

@ -0,0 +1,9 @@
package gr.cite.notification.config.email;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(EmailProperties.class)
public class EmailConfig {
}

View File

@ -0,0 +1,17 @@
package gr.cite.notification.config.email;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "email")
public class EmailProperties {
private final String address;
public EmailProperties(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
}

View File

@ -0,0 +1,9 @@
package gr.cite.notification.config.formatting;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(FormattingServiceProperties.class)
public class FormattingServiceConfiguration {
}

View File

@ -0,0 +1,38 @@
package gr.cite.notification.config.formatting;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "formatting.options")
public class FormattingServiceProperties {
private final String integerFormat;
private final String decimalFormat;
private final String dateTimeFormat;
private final Integer decimalDigitsRound;
public FormattingServiceProperties(String integerFormat, String decimalFormat, String dateTimeFormat, Integer decimalDigitsRound) {
this.integerFormat = integerFormat;
this.decimalFormat = decimalFormat;
this.dateTimeFormat = dateTimeFormat;
this.decimalDigitsRound = decimalDigitsRound;
}
public String getIntegerFormat() {
return integerFormat;
}
public String getDecimalFormat() {
return decimalFormat;
}
public String getDateTimeFormat() {
return dateTimeFormat;
}
public Integer getDecimalDigitsRound() {
return decimalDigitsRound;
}
}

View File

@ -0,0 +1,10 @@
package gr.cite.notification.config.formatting;
import gr.cite.tools.cache.CacheOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "cache.formatting-user-profile-cache")
public class FormattingUserprofileCacheOptions extends CacheOptions {
}

View File

@ -0,0 +1,41 @@
package gr.cite.notification.convention;
import gr.cite.tools.exception.MyApplicationException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
public interface ConventionService {
Boolean isValidId(Integer id);
Boolean isValidGuid(UUID guid);
Boolean isValidUUID(String str);
UUID parseUUIDSafe(String str);
Boolean isValidHash(String hash);
String hashValue(Object value) throws MyApplicationException;
String limit(String text, int maxLength);
String truncate(String text, int maxLength);
UUID getEmptyUUID();
boolean isNullOrEmpty(String value);
boolean isListNullOrEmpty(List value);
String stringEmpty();
String asPrefix(String name);
String asIndexerPrefix(String part);
String asIndexer(String... names);
<K, V> Map<K, List<V>> toDictionaryOfList(List<V> items, Function<V, K> keySelector);
}

View File

@ -0,0 +1,150 @@
package gr.cite.notification.convention;
import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.tools.exception.MyApplicationException;
import gr.cite.tools.logging.LoggerService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class ConventionServiceImpl implements ConventionService {
private final static Pattern UUID_REGEX_PATTERN = Pattern.compile("^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$");
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(ConventionServiceImpl.class));
private final ErrorThesaurusProperties errors;
@Autowired
public ConventionServiceImpl(ErrorThesaurusProperties errors) {
this.errors = errors;
}
@Override
public Boolean isValidId(Integer id) {
return id != null && id > 0;
}
@Override
public Boolean isValidGuid(UUID guid) {
return guid != null && !guid.equals(this.getEmptyUUID());
}
@Override
public Boolean isValidUUID(String str) {
if (this.isNullOrEmpty(str)) {
return false;
}
return UUID_REGEX_PATTERN.matcher(str).matches();
}
@Override
public UUID parseUUIDSafe(String str) {
if (!this.isValidUUID(str)) {
return null;
}
try {
return UUID.fromString(str);
} catch (Exception ex){
logger.warn("invalid uuid" + str, ex);
return null;
}
}
@Override
public Boolean isValidHash(String hash) {
return !this.isNullOrEmpty(hash);
}
@Override
public String hashValue(Object value) throws MyApplicationException {
if (value == null) return this.stringEmpty();
if (value instanceof Instant) return String.format("%ts", (Instant) value);
throw new MyApplicationException(this.errors.getSystemError().getCode(), this.errors.getSystemError().getMessage());
}
@Override
public String limit(String text, int maxLength) {
if (this.isNullOrEmpty(text)) return text;
if (text.length() > maxLength) return String.format("%s...", text.substring(0, maxLength));
else return text;
}
@Override
public String truncate(String text, int maxLength) {
String truncated = text;
if (text.length() < maxLength) return text;
truncated = truncated.trim();
truncated = truncated.replaceAll("\\s+", " ");//remove multiple spaces
if (truncated.length() < maxLength) return truncated;
truncated = truncated.replaceAll("([.!@#$%^&-=':;,<>?*\\\"/|])+", "");//remove multiple spaces
if (truncated.length() < maxLength) return truncated;
truncated = truncated.replaceAll("([aeiou])+", "");//remove multiple spaces
if (truncated.length() < maxLength) return truncated;
truncated = truncated.replaceAll("([AEIOU])+", "");//remove multiple spaces
if (truncated.length() < maxLength) return truncated;
if (text.length() > maxLength) return String.format("%s...", text.substring(0, maxLength));
return text;
}
@Override
public UUID getEmptyUUID() {
return new UUID(0L, 0L);
}
@Override
public boolean isNullOrEmpty(String value) {
return value == null || value.isBlank();
}
@Override
public boolean isListNullOrEmpty(List value) {
if(value == null) return true;
return value.size() <= 0;
}
@Override
public String stringEmpty() {
return "";
}
@Override
public String asPrefix(String name) {
if (name == null) return null;
return name + ".";
}
@Override
public String asIndexer(String... names) {
if (names == null) return null;
return String.join(".", Arrays.stream(names).filter(x -> !this.isNullOrEmpty(x)).collect(Collectors.toList()));
}
@Override
public String asIndexerPrefix(String part) {
if (part == null) return null;
return part + ".";
}
@Override
public <K, V> Map<K, List<V>> toDictionaryOfList(List<V> items, Function<V, K> keySelector) {
Map<K, List<V>> map = new HashMap<>();
for (V model : items) {
K key = keySelector.apply(model);
if (!map.containsKey(key)) map.put(key, new ArrayList<V>());
map.get(key).add(model);
}
return map;
}
}

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