Compare commits

...

2 Commits

422 changed files with 0 additions and 31468 deletions

View File

@ -1,38 +0,0 @@
####################################### Build stage #######################################
FROM maven:3.9-eclipse-temurin-21-alpine AS build-stage
ARG MAVEN_ACCOUNT_USR
ARG MAVEN_ACCOUNT_PSW
ARG REVISION
ARG PROFILE
ENV server_username=$MAVEN_ACCOUNT_USR
ENV server_password=$MAVEN_ACCOUNT_PSW
ARG CITE_MAVEN_REPO_URL
COPY pom.xml /build/
COPY notification /build/notification/
COPY notification-web /build/notification-web/
COPY settings.xml /root/.m2/settings.xml
RUN rm -f /build/notification-web/src/main/resources/config/app.env
RUN rm -f /build/notification-web/src/main/resources/config/*-devel.yml
RUN rm -f /build/notification-web/src/main/resources/logging/*.xml
RUN rm -f /build/notification-web/src/main/resources/certificates/*.crt
WORKDIR /build/
RUN mvn -Drevision=${REVISION} -DciteMavenRepoUrl=${CITE_MAVEN_REPO_URL} -P${PROFILE} clean
RUN mvn -Drevision=${REVISION} -DciteMavenRepoUrl=${CITE_MAVEN_REPO_URL} -P${PROFILE} install
# Build project
RUN mvn -Drevision=${REVISION} -DciteMavenRepoUrl=${CITE_MAVEN_REPO_URL} -P${PROFILE} package
######################################## Run Stage ########################################
FROM eclipse-temurin:21-jre-ubi9-minimal
ARG PROFILE
ARG REVISION
ENV SERVER_PORT=8080
EXPOSE ${SERVER_PORT}
COPY --from=build-stage /build/notification-web/target/notification-web-${REVISION}.jar /app/notification-web.jar
ENTRYPOINT ["java","-Dspring.config.additional-location=file:/config/","-Dspring.profiles.active=${PROFILE}","-Djava.security.egd=file:/dev/./urandom","-jar","/app/notification-web.jar"]

View File

@ -1,30 +0,0 @@
####################################### Build stage #######################################
FROM maven:3.9-eclipse-temurin-21-alpine
ARG MAVEN_ACCOUNT_USR
ARG MAVEN_ACCOUNT_PSW
ARG REVISION
ARG PROFILE
ARG ORACLE_URL
ARG ORACLE_TOKEN
ENV server_username=$MAVEN_ACCOUNT_USR
ENV server_password=$MAVEN_ACCOUNT_PSW
ARG CITE_MAVEN_REPO_URL
COPY pom.xml /build/
COPY notification /build/notification/
COPY notification-web /build/notification-web/
COPY settings.xml /root/.m2/settings.xml
RUN rm -f /build/notification-web/src/main/resources/config/app.env
RUN rm -f /build/notification-web/src/main/resources/config/*-devel.yml
RUN rm -f /build/notification-web/src/main/resources/logging/*.xml
RUN rm -f /build/notification-web/src/main/resources/certificates/*.crt
COPY oracle.local.cite.gr.crt $JAVA_HOME/conf/security
RUN cd "$JAVA_HOME"/conf/security && keytool -cacerts -storepass changeit -noprompt -trustcacerts -importcert -alias oraclecert -file oracle.local.cite.gr.crt
WORKDIR /build/
RUN mvn -Drevision=${REVISION} -DciteMavenRepoUrl=${CITE_MAVEN_REPO_URL} -P${PROFILE} clean
RUN mvn -Drevision=${REVISION} -DciteMavenRepoUrl=${CITE_MAVEN_REPO_URL} -P${PROFILE} install
RUN mvn -Drevision=${REVISION} -DciteMavenRepoUrl=${CITE_MAVEN_REPO_URL} -P${PROFILE} package
RUN mvn sonar:sonar -Drevision=${REVISION} -DciteMavenRepoUrl=${CITE_MAVEN_REPO_URL} -P${PROFILE} -Dsonar.projectKey=OpenDMP:notification-api -Dsonar.login=${ORACLE_TOKEN} -Dsonar.host.url=${ORACLE_URL} -Dsonar.projectName='OpenDMP Notification API'

View File

@ -1,33 +0,0 @@
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

@ -1,87 +0,0 @@
<?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>notification-service-parent</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>notification-web</artifactId>
<packaging>jar</packaging>
<properties>
<java.version>21</java.version>
<maven.compiler.release>21</maven.compiler.release>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<revision>1.0.0-SNAPSHOT</revision>
</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>notification</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>gr.cite</groupId>
<artifactId>oidc-authz</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>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,26 +0,0 @@
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 = {
"gr.cite.notification.*",
"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 NotificationApplication {
public static void main(String[] args) {
SpringApplication.run(NotificationApplication.class, args);
}
}

View File

@ -1,40 +0,0 @@
package gr.cite.notification.web;
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 gr.cite.notification.web.scope.user.UserInterceptor;
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

@ -1,44 +0,0 @@
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

@ -1,25 +0,0 @@
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

@ -1,144 +0,0 @@
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 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 jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
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 new ArrayList<>();
}
};
}
@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

@ -1,193 +0,0 @@
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

@ -1,198 +0,0 @@
package gr.cite.notification.web.controllers;
import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.authorization.AuthorizationFlags;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.enums.NotificationInAppTracking;
import gr.cite.notification.common.scope.user.UserScope;
import gr.cite.notification.data.InAppNotificationEntity;
import gr.cite.notification.data.TenantEntityManager;
import gr.cite.notification.errorcode.ErrorThesaurusProperties;
import gr.cite.notification.model.InAppNotification;
import gr.cite.notification.model.Notification;
import gr.cite.notification.model.builder.InAppNotificationBuilder;
import gr.cite.notification.model.censorship.InAppNotificationCensor;
import gr.cite.notification.query.InAppNotificationQuery;
import gr.cite.notification.query.lookup.InAppNotificationLookup;
import gr.cite.notification.service.inappnotification.InAppNotificationService;
import gr.cite.notification.service.notification.NotificationService;
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.BaseFieldSet;
import gr.cite.tools.fieldset.FieldSet;
import gr.cite.tools.logging.LoggerService;
import gr.cite.tools.logging.MapLogEntry;
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 jakarta.transaction.Transactional;
import java.util.*;
@RestController
@RequestMapping(path = "api/notification/inapp-notification")
public class InAppNotificationController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(InAppNotificationController.class));
private final BuilderFactory builderFactory;
private final AuditService auditService;
private final CensorFactory censorFactory;
private final QueryFactory queryFactory;
private final MessageSource messageSource;
private final InAppNotificationService inAppNotificationService;
private final UserScope userScope;
private final ErrorThesaurusProperties errors;
private final TenantEntityManager tenantEntityManager;
@Autowired
public InAppNotificationController(BuilderFactory builderFactory,
AuditService auditService,
NotificationService notificationService, CensorFactory censorFactory,
QueryFactory queryFactory,
MessageSource messageSource,
InAppNotificationService inAppNotificationService, UserScope userScope, ErrorThesaurusProperties errors, TenantEntityManager tenantEntityManager) {
this.builderFactory = builderFactory;
this.auditService = auditService;
this.censorFactory = censorFactory;
this.queryFactory = queryFactory;
this.messageSource = messageSource;
this.inAppNotificationService = inAppNotificationService;
this.userScope = userScope;
this.errors = errors;
this.tenantEntityManager = tenantEntityManager;
}
@PostMapping("query")
public QueryResult<InAppNotification> Query(@RequestBody InAppNotificationLookup lookup) throws MyApplicationException, MyForbiddenException, InvalidApplicationException {
logger.debug("querying {}", InAppNotification.class.getSimpleName());
UUID userId = this.userScope.getUserId();
this.censorFactory.censor(InAppNotificationCensor.class).censor(lookup.getProject(), userId);
try {
this.tenantEntityManager.loadExplictTenantFilters();
if (userId == null) throw new MyForbiddenException(this.errors.getNonPersonPrincipal().getCode(), this.errors.getNonPersonPrincipal().getMessage());
InAppNotificationQuery query = lookup.enrich(this.queryFactory).disableTracking().userId(userId);
List<InAppNotificationEntity> data = query.collectAs(lookup.getProject());
List<InAppNotification> models = this.builderFactory.builder(InAppNotificationBuilder.class).build(lookup.getProject(), data);
long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size();
this.auditService.track(AuditableAction.InApp_Notification_Query, "lookup", lookup);
return new QueryResult<>(models, count);
} finally {
this.tenantEntityManager.reloadTenantFilters();
}
}
@GetMapping("{id}")
@Transactional
public InAppNotification Get(@PathVariable UUID id, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException {
logger.debug(new MapLogEntry("retrieving" + InAppNotification.class.getSimpleName()).And("id", id).And("fields", fieldSet));
UUID userId = this.userScope.getUserId();
this.censorFactory.censor(InAppNotificationCensor.class).censor(fieldSet, userId);
try {
this.tenantEntityManager.loadExplictTenantFilters();
InAppNotificationQuery query = this.queryFactory.query(InAppNotificationQuery.class).disableTracking().authorize(AuthorizationFlags.OwnerOrPermission).userId(userId).ids(id);
InAppNotification model = this.builderFactory.builder(InAppNotificationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.firstAs(fieldSet));
if (model == null)
throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, Notification.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.auditService.track(AuditableAction.InApp_Notification_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;
} finally {
this.tenantEntityManager.reloadTenantFilters();
}
}
@PostMapping("{id}/read")
@Transactional
public Boolean persist(@PathVariable UUID id) throws InvalidApplicationException {
logger.debug(new MapLogEntry("marking as read").And("id", id));
try {
this.tenantEntityManager.loadExplictTenantFilters();
this.inAppNotificationService.markAsRead(id);
this.auditService.track(AuditableAction.InApp_Notification_Read, Map.of("id", id));
//this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
return true;
} finally {
this.tenantEntityManager.reloadTenantFilters();
}
}
@PostMapping("read-all")
@Transactional
public Boolean MarkAsReadAllUserInAppNotification() throws InvalidApplicationException {
logger.debug(new MapLogEntry("marking as read all"));
UUID userId = this.userScope.getUserId();
if (userId == null) throw new MyForbiddenException(this.errors.getNonPersonPrincipal().getCode(), this.errors.getNonPersonPrincipal().getMessage());
try {
this.tenantEntityManager.loadExplictTenantFilters();
this.inAppNotificationService.markAsReadAllUserNotification(userId);
this.auditService.track(AuditableAction.InApp_Notification_Read_All, Map.of("userId", userId));
return true;
} finally {
this.tenantEntityManager.reloadTenantFilters();
}
}
@GetMapping("count-unread")
@Transactional
public Integer CountUnread() throws InvalidApplicationException {
logger.debug("count-unread");
UUID userId = this.userScope.getUserId();
if (userId == null) throw new MyForbiddenException(this.errors.getNonPersonPrincipal().getCode(), this.errors.getNonPersonPrincipal().getMessage());
this.censorFactory.censor(InAppNotificationCensor.class).censor(new BaseFieldSet(InAppNotification.Field.ID), userId);
try {
this.tenantEntityManager.loadExplictTenantFilters();
InAppNotificationQuery query = this.queryFactory.query(InAppNotificationQuery.class).disableTracking().isActive(IsActive.Active).trackingState(NotificationInAppTracking.STORED).userId(userId);
int count = Math.toIntExact(query.count());
//this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
return count;
} finally {
this.tenantEntityManager.reloadTenantFilters();
}
}
@DeleteMapping("{id}")
@Transactional
public void Delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException {
logger.debug(new MapLogEntry("deleting" + Notification.class.getSimpleName()).And("id", id));
try {
this.tenantEntityManager.loadExplictTenantFilters();
this.inAppNotificationService.deleteAndSave(id);
this.auditService.track(AuditableAction.InApp_Notification_Delete, "id", id);
//this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
} finally {
this.tenantEntityManager.reloadTenantFilters();
}
}
}

View File

@ -1,127 +0,0 @@
package gr.cite.notification.web.controllers;
import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.authorization.AuthorizationFlags;
import gr.cite.notification.data.NotificationEntity;
import gr.cite.notification.model.Notification;
import gr.cite.notification.model.builder.NotificationBuilder;
import gr.cite.notification.model.censorship.NotificationCensor;
import gr.cite.notification.model.persist.NotificationPersist;
import gr.cite.notification.query.NotificationQuery;
import gr.cite.notification.query.lookup.NotificationLookup;
import gr.cite.notification.service.notification.NotificationService;
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")
public class NotificationController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationController.class));
private final BuilderFactory builderFactory;
private final AuditService auditService;
private final NotificationService notificationService;
private final CensorFactory censorFactory;
private final QueryFactory queryFactory;
private final MessageSource messageSource;
@Autowired
public NotificationController(BuilderFactory builderFactory,
AuditService auditService,
NotificationService notificationService, CensorFactory censorFactory,
QueryFactory queryFactory,
MessageSource messageSource) {
this.builderFactory = builderFactory;
this.auditService = auditService;
this.notificationService = notificationService;
this.censorFactory = censorFactory;
this.queryFactory = queryFactory;
this.messageSource = messageSource;
}
@PostMapping("query")
public QueryResult<Notification> Query(@RequestBody NotificationLookup lookup) throws MyApplicationException, MyForbiddenException {
logger.debug("querying {}", Notification.class.getSimpleName());
this.censorFactory.censor(NotificationCensor.class).censor(lookup.getProject());
NotificationQuery query = lookup.enrich(this.queryFactory).disableTracking().authorize(AuthorizationFlags.OwnerOrPermission);
List<NotificationEntity> data = query.collectAs(lookup.getProject());
List<Notification> models = this.builderFactory.builder(NotificationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(lookup.getProject(), data);
long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size();
this.auditService.track(AuditableAction.Notification_Query, "lookup", lookup);
return new QueryResult<>(models, count);
}
@GetMapping("{id}")
@Transactional
public Notification Get(@PathVariable UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
logger.debug(new MapLogEntry("retrieving" + Notification.class.getSimpleName()).And("id", id).And("fields", fieldSet));
this.censorFactory.censor(NotificationCensor.class).censor(fieldSet);
NotificationQuery query = this.queryFactory.query(NotificationQuery.class).disableTracking().authorize(AuthorizationFlags.OwnerOrPermission).ids(id);
Notification model = this.builderFactory.builder(NotificationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.firstAs(fieldSet));
if (model == null)
throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, Notification.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.auditService.track(AuditableAction.Notification_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")
@Transactional
@ValidationFilterAnnotation(validator = NotificationPersist.NotificationPersistValidator.ValidatorName, argumentName = "model")
public Notification Persist(@RequestBody NotificationPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException {
logger.debug(new MapLogEntry("persisting" + Notification.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
Notification persisted = this.notificationService.persist(model, fieldSet);
this.auditService.track(AuditableAction.Notification_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", model),
new AbstractMap.SimpleEntry<String, Object>("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" + Notification.class.getSimpleName()).And("id", id));
this.notificationService.deleteAndSave(id);
this.auditService.track(AuditableAction.InApp_Notification_Delete, "id", id);
//this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
}
}

View File

@ -1,123 +0,0 @@
package gr.cite.notification.web.controllers;
import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.authorization.AuthorizationFlags;
import gr.cite.notification.data.NotificationTemplateEntity;
import gr.cite.notification.model.NotificationTemplate;
import gr.cite.notification.model.builder.NotificationTemplateBuilder;
import gr.cite.notification.model.censorship.NotificationTemplateCensor;
import gr.cite.notification.model.persist.NotificationTemplatePersist;
import gr.cite.notification.query.NotificationTemplateQuery;
import gr.cite.notification.query.lookup.NotificationTemplateLookup;
import gr.cite.notification.service.notificationtemplate.NotificationTemplateService;
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 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 jakarta.transaction.Transactional;
import java.util.*;
@RestController
@RequestMapping(path = "api/notification/notification-template")
public class NotificationTemplateController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(NotificationTemplateController.class));
private final BuilderFactory builderFactory;
private final AuditService auditService;
private final NotificationTemplateService notificationTemplateService;
private final CensorFactory censorFactory;
private final QueryFactory queryFactory;
private final MessageSource messageSource;
@Autowired
public NotificationTemplateController(BuilderFactory builderFactory,
AuditService auditService,
NotificationTemplateService notificationTemplateService,
CensorFactory censorFactory,
QueryFactory queryFactory,
MessageSource messageSource) {
this.builderFactory = builderFactory;
this.auditService = auditService;
this.notificationTemplateService = notificationTemplateService;
this.censorFactory = censorFactory;
this.queryFactory = queryFactory;
this.messageSource = messageSource;
}
@PostMapping("query")
public QueryResult<NotificationTemplate> query(@RequestBody NotificationTemplateLookup lookup) throws MyApplicationException, MyForbiddenException {
logger.debug("querying {}", NotificationTemplate.class.getSimpleName());
this.censorFactory.censor(NotificationTemplateCensor.class).censor(lookup.getProject());
NotificationTemplateQuery query = lookup.enrich(this.queryFactory).disableTracking().authorize(AuthorizationFlags.OwnerOrPermission);
List<NotificationTemplateEntity> data = query.collectAs(lookup.getProject());
List<NotificationTemplate> models = this.builderFactory.builder(NotificationTemplateBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(lookup.getProject(), data);
long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size();
this.auditService.track(AuditableAction.Notification_Template_Query, "lookup", lookup);
return new QueryResult<>(models, count);
}
@GetMapping("{id}")
public NotificationTemplate get(@PathVariable UUID id, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
logger.debug(new MapLogEntry("retrieving" + NotificationTemplate.class.getSimpleName()).And("id", id).And("fields", fieldSet));
this.censorFactory.censor(NotificationTemplateCensor.class).censor(fieldSet);
NotificationTemplateQuery query = this.queryFactory.query(NotificationTemplateQuery.class).disableTracking().authorize(AuthorizationFlags.OwnerOrPermission).ids(id);
NotificationTemplate model = this.builderFactory.builder(NotificationTemplateBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.first());
if (model == null)
throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, NotificationTemplate.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.auditService.track(AuditableAction.Notification_Template_Lookup, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", id),
new AbstractMap.SimpleEntry<String, Object>("fields", fieldSet)
));
return model;
}
@PostMapping("persist")
@Transactional
@ValidationFilterAnnotation(validator = NotificationTemplatePersist.NotificationTemplatePersistValidator.ValidatorName, argumentName = "model")
public NotificationTemplate persist(@RequestBody NotificationTemplatePersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException {
logger.debug(new MapLogEntry("persisting" + NotificationTemplate.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
NotificationTemplate persisted = this.notificationTemplateService.persist(model, fieldSet);
this.auditService.track(AuditableAction.Notification_Template_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", model),
new AbstractMap.SimpleEntry<String, Object>("fields", fieldSet)
));
return persisted;
}
@DeleteMapping("{id}")
@Transactional
public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException {
logger.debug(new MapLogEntry("deleting" + NotificationTemplate.class.getSimpleName()).And("id", id));
this.notificationTemplateService.deleteAndSave(id);
this.auditService.track(AuditableAction.Notification_Template_Delete, "id", id);
}
}

View File

@ -1,75 +0,0 @@
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/notification/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;
@Autowired
public PrincipalController(
CurrentPrincipalResolver currentPrincipalResolver,
AccountBuilder accountBuilder,
AuditService auditService) {
this.currentPrincipalResolver = currentPrincipalResolver;
this.accountBuilder = accountBuilder;
this.auditService = auditService;
}
@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;
}
}

View File

@ -1,179 +0,0 @@
package gr.cite.notification.web.controllers;
import com.fasterxml.jackson.core.JsonProcessingException;
import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.authorization.AuthorizationFlags;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.enums.TenantConfigurationType;
import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.common.types.tenantconfiguration.NotifierListTenantConfigurationEntity;
import gr.cite.notification.data.TenantConfigurationEntity;
import gr.cite.notification.model.builder.tenantconfiguration.TenantConfigurationBuilder;
import gr.cite.notification.model.censorship.tenantconfiguration.TenantConfigurationCensor;
import gr.cite.notification.model.persist.tenantconfiguration.TenantConfigurationPersist;
import gr.cite.notification.model.tenantconfiguration.TenantConfiguration;
import gr.cite.notification.query.TenantConfigurationQuery;
import gr.cite.notification.query.lookup.NotifierListLookup;
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 org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.management.InvalidApplicationException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@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;
private final TenantScope tenantScope;
public TenantConfigurationController(
BuilderFactory builderFactory,
AuditService auditService,
TenantConfigurationService tenantConfigurationService, CensorFactory censorFactory,
QueryFactory queryFactory,
MessageSource messageSource, TenantScope tenantScope) {
this.builderFactory = builderFactory;
this.auditService = auditService;
this.tenantConfigurationService = tenantConfigurationService;
this.censorFactory = censorFactory;
this.queryFactory = queryFactory;
this.messageSource = messageSource;
this.tenantScope = tenantScope;
}
@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(), null);
TenantConfigurationQuery query = lookup.enrich(this.queryFactory).disableTracking().authorize(AuthorizationFlags.OwnerOrPermission);
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}")
public TenantConfiguration get(@PathVariable("id") UUID id, FieldSet fieldSet) 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, null);
TenantConfigurationQuery query = this.queryFactory.query(TenantConfigurationQuery.class).disableTracking().authorize(AuthorizationFlags.OwnerOrPermission).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)
));
return model;
}
@GetMapping("current-tenant/{type}")
public TenantConfiguration getCurrentTenantType(@PathVariable("type") Short type, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException {
logger.debug(new MapLogEntry("retrieving" + TenantConfiguration.class.getSimpleName()).And("type", type).And("fields", fieldSet));
this.censorFactory.censor(TenantConfigurationCensor.class).censor(fieldSet, null);
TenantConfigurationQuery query = this.queryFactory.query(TenantConfigurationQuery.class).disableTracking().authorize(AuthorizationFlags.OwnerOrPermission).isActive(IsActive.Active).types(TenantConfigurationType.of(type));
if (this.tenantScope.isDefaultTenant()) query.tenantIsSet(false);
else query.tenantIsSet(true).tenantIds(this.tenantScope.getTenant());
TenantConfiguration model = this.builderFactory.builder(TenantConfigurationBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.firstAs(fieldSet));
this.auditService.track(AuditableAction.TenantConfiguration_LookupByType, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("type", type),
new AbstractMap.SimpleEntry<String, Object>("fields", fieldSet)
));
return model;
}
@PostMapping("persist")
@org.springframework.transaction.annotation.Transactional
@ValidationFilterAnnotation(validator = TenantConfigurationPersist.TenantConfigurationPersistValidator.ValidatorName, argumentName = "model")
public TenantConfiguration persist(@RequestBody TenantConfigurationPersist model, FieldSet fieldSet) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException, JsonProcessingException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
logger.debug(new MapLogEntry("persisting" + TenantConfiguration.class.getSimpleName()).And("model", model).And("fieldSet", fieldSet));
TenantConfiguration persisted = this.tenantConfigurationService.persist(model, fieldSet);
this.auditService.track(AuditableAction.Tenant_Configuration_Persist, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("model", model),
new AbstractMap.SimpleEntry<String, Object>("fields", fieldSet)
));
return persisted;
}
@DeleteMapping("{id}")
@Transactional
public void delete(@PathVariable("id") UUID id) throws MyForbiddenException, InvalidApplicationException {
logger.debug(new MapLogEntry("retrieving" + TenantConfiguration.class.getSimpleName()).And("id", id));
this.tenantConfigurationService.deleteAndSave(id);
this.auditService.track(AuditableAction.Tenant_Configuration_Delete, "id", id);
}
@PostMapping("notifier-list/available")
public NotifierListTenantConfigurationEntity getAvailableNotifiers(@RequestBody NotifierListLookup tenantNotifierListLookup)
{
logger.debug("querying available notifiers");
NotifierListTenantConfigurationEntity notifierListData = this.tenantConfigurationService.collectTenantAvailableNotifierList(tenantNotifierListLookup.getNotificationTypes());
this.auditService.track(AuditableAction.Tenant_Configuration_Notifiers_Query, Map.of(
"lookup", tenantNotifierListLookup
));
//this._auditService.TrackIdentity(AuditableAction.IdentityTracking_Action);
return notifierListData;
}
}

View File

@ -1,163 +0,0 @@
package gr.cite.notification.web.controllers;
import gr.cite.notification.audit.AuditableAction;
import gr.cite.notification.authorization.AuthorizationFlags;
import gr.cite.notification.common.enums.IsActive;
import gr.cite.notification.common.enums.TenantConfigurationType;
import gr.cite.notification.common.scope.tenant.TenantScope;
import gr.cite.notification.common.types.tenantconfiguration.NotifierListTenantConfigurationEntity;
import gr.cite.notification.data.TenantEntity;
import gr.cite.notification.data.UserNotificationPreferenceEntity;
import gr.cite.notification.event.TenantConfigurationTouchedEvent;
import gr.cite.notification.model.UserNotificationPreference;
import gr.cite.notification.model.builder.UserNotificationPreferenceBuilder;
import gr.cite.notification.model.censorship.UserNotificationPreferenceCensor;
import gr.cite.notification.model.persist.UserNotificationPreferencePersist;
import gr.cite.notification.query.UserNotificationPreferenceQuery;
import gr.cite.notification.query.lookup.NotifierListLookup;
import gr.cite.notification.query.lookup.UserNotificationPreferenceLookup;
import gr.cite.notification.service.userNotificationPreference.UserNotificationPreferenceService;
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.Ordering;
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 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 jakarta.transaction.Transactional;
import javax.management.InvalidApplicationException;
import java.util.*;
@RestController
@RequestMapping(path = "api/notification/notification-preference")
public class UserNotificationPreferenceController {
private static final LoggerService logger = new LoggerService(LoggerFactory.getLogger(UserNotificationPreferenceController.class));
private final BuilderFactory builderFactory;
private final AuditService auditService;
private final UserNotificationPreferenceService userNotificationPreferenceService;
private final CensorFactory censorFactory;
private final QueryFactory queryFactory;
private final MessageSource messageSource;
private final TenantScope tenantScope;
@Autowired
public UserNotificationPreferenceController(BuilderFactory builderFactory,
AuditService auditService,
UserNotificationPreferenceService userNotificationPreferenceService,
CensorFactory censorFactory,
QueryFactory queryFactory,
MessageSource messageSource, TenantScope tenantScope) {
this.builderFactory = builderFactory;
this.auditService = auditService;
this.userNotificationPreferenceService = userNotificationPreferenceService;
this.censorFactory = censorFactory;
this.queryFactory = queryFactory;
this.messageSource = messageSource;
this.tenantScope = tenantScope;
}
@PostMapping("query")
public QueryResult<UserNotificationPreference> query(@RequestBody UserNotificationPreferenceLookup lookup) throws MyApplicationException, MyForbiddenException {
logger.debug("querying {}", UserNotificationPreference.class.getSimpleName());
this.censorFactory.censor(UserNotificationPreferenceCensor.class).censor(lookup.getProject(), null);
UserNotificationPreferenceQuery query = lookup.enrich(this.queryFactory).disableTracking();
List<UserNotificationPreferenceEntity> data = query.collectAs(lookup.getProject());
List<UserNotificationPreference> models = this.builderFactory.builder(UserNotificationPreferenceBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(lookup.getProject(), data);
long count = (lookup.getMetadata() != null && lookup.getMetadata().getCountAll()) ? query.count() : models.size();
this.auditService.track(AuditableAction.User_Notification_Preference_Query, "lookup", lookup);
return new QueryResult<>(models, count);
}
@GetMapping("user/{userId}/current")
@Transactional
public List<UserNotificationPreference> current(@PathVariable UUID userId, FieldSet fieldSet, Locale locale) throws MyApplicationException, MyForbiddenException, MyNotFoundException, InvalidApplicationException {
logger.debug(new MapLogEntry("retrieving" + UserNotificationPreference.class.getSimpleName()).And("userId", userId).And("fields", fieldSet));
this.censorFactory.censor(UserNotificationPreferenceCensor.class).censor(fieldSet, userId);
Ordering ordering = new Ordering();
ordering.addAscending(UserNotificationPreference._ordinal);
UserNotificationPreferenceQuery query = this.queryFactory.query(UserNotificationPreferenceQuery.class).disableTracking().userId(userId).isActives(IsActive.Active);
query.setOrder(ordering);
if (this.tenantScope.isMultitenant() && this.tenantScope.isSet()) {
if (!this.tenantScope.isDefaultTenant()) {
query.tenantIsSet(true).tenantIds(this.tenantScope.getTenant());
} else {
query.tenantIsSet(false);
}
}
List<UserNotificationPreference> model = this.builderFactory.builder(UserNotificationPreferenceBuilder.class).authorize(AuthorizationFlags.OwnerOrPermission).build(fieldSet, query.collectAs(fieldSet));
this.auditService.track(AuditableAction.User_Notification_Preference_Lookup, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("userId", userId),
new AbstractMap.SimpleEntry<String, Object>("fields", fieldSet)
));
//this.auditService.trackIdentity(AuditableAction.IdentityTracking_Action);
return model;
}
@PostMapping("notifier-list/available")
public NotifierListTenantConfigurationEntity getAvailableNotifiers(@RequestBody NotifierListLookup tenantNotifierListLookup)
{
logger.debug("querying available notifiers");
NotifierListTenantConfigurationEntity notifierListData = this.userNotificationPreferenceService.collectUserAvailableNotifierList(tenantNotifierListLookup.getNotificationTypes());
this.auditService.track(AuditableAction.User_Available_Notifiers_Query, Map.of(
"lookup", tenantNotifierListLookup
));
//this._auditService.TrackIdentity(AuditableAction.IdentityTracking_Action);
return notifierListData;
}
@PostMapping("persist")
@Transactional
@ValidationFilterAnnotation(validator = UserNotificationPreferencePersist.UserNotificationPreferencePersistValidator.ValidatorName, argumentName = "model")
public List<UserNotificationPreference> persist(@RequestBody UserNotificationPreferencePersist model, FieldSet fieldSet)
{
logger.debug(new MapLogEntry("persisting").And("type", TenantConfigurationType.NotifierList).And("model", model).And("fields", fieldSet));
List<UserNotificationPreference> persisted = this.userNotificationPreferenceService.persist(model, fieldSet);
this.auditService.track(AuditableAction.User_Notification_Preference_Persist, Map.of(
"type", TenantConfigurationType.NotifierList,
"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

@ -1,17 +0,0 @@
//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

@ -1,160 +0,0 @@
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

@ -1,86 +0,0 @@
package gr.cite.notification.web.model;
import gr.cite.commons.web.authz.configuration.AuthorizationConfiguration;
import gr.cite.commons.web.authz.configuration.Permission;
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.*;
@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);
for (Map.Entry<String, Permission> permissionEntry : authorizationConfiguration.getRawPolicies().entrySet()){
if (permissionEntry.getValue().getAllowAuthenticated()){
permissions.add(permissionEntry.getKey());
}
}
model.setPermissions(new ArrayList<>(permissions));
}
return model;
}
}

View File

@ -1,37 +0,0 @@
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

@ -1,10 +0,0 @@
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

@ -1,76 +0,0 @@
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()));
}
@Override
protected Class<TenantByCodeCacheValue> valueClass() {
return TenantByCodeCacheValue.class;
}
@Override
public String keyOf(TenantByCodeCacheValue value) {
return this.buildKey(value.getTenantCode());
}
public String buildKey(String code) {
HashMap<String, String> keyParts = new HashMap<>();
keyParts.put("$code$", code);
return this.generateKey(keyParts);
}
}

View File

@ -1,10 +0,0 @@
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

@ -1,77 +0,0 @@
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 (event.getTenantId() != null)
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) {
HashMap<String, String> keyParts = new HashMap<>();
keyParts.put("$tenantId$", id.toString().toLowerCase(Locale.ROOT));
return this.generateKey(keyParts);
}
}

View File

@ -1,184 +0,0 @@
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.ClaimNames;
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.TenantEntityManager;
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.notification.query.utils.BuildSubQueryInput;
import gr.cite.notification.query.utils.QueryUtilsService;
import gr.cite.tools.exception.MyForbiddenException;
import gr.cite.tools.logging.LoggerService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import org.hibernate.Session;
import org.jetbrains.annotations.NotNull;
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;
import java.util.UUID;
@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 TenantScopeProperties tenantScopeProperties;
private final UserAllowedTenantCacheService userAllowedTenantCacheService;
private final ErrorThesaurusProperties errors;
private final QueryUtilsService queryUtilsService;
public final TenantEntityManager tenantEntityManager;
@PersistenceContext
public EntityManager entityManager;
@Autowired
public TenantInterceptor(
TenantScope tenantScope,
UserScope userScope,
CurrentPrincipalResolver currentPrincipalResolver,
ClaimExtractor claimExtractor,
ApplicationContext applicationContext,
TenantScopeProperties tenantScopeProperties,
UserAllowedTenantCacheService userAllowedTenantCacheService,
ErrorThesaurusProperties errors, QueryUtilsService queryUtilsService, TenantEntityManager tenantEntityManager) {
this.tenantScope = tenantScope;
this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.applicationContext = applicationContext;
this.tenantScopeProperties = tenantScopeProperties;
this.userAllowedTenantCacheService = userAllowedTenantCacheService;
this.errors = errors;
this.queryUtilsService = queryUtilsService;
this.tenantEntityManager = tenantEntityManager;
}
@Override
public void preHandle(@NotNull WebRequest request) throws InvalidApplicationException, InterruptedException {
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(), ClaimNames.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;
if (this.tenantScope.isDefaultTenant()){
isUserAllowedTenant = true;
} else {
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.tenantEntityManager.reloadTenantFilters();
} 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.errors.getMissingTenant().getCode(), this.errors.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, InterruptedException {
if (userScope.isSet()) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<UserEntity> query = criteriaBuilder.createQuery(UserEntity.class);
Root<UserEntity> root = query.from(UserEntity.class);
query.where(criteriaBuilder.and(
criteriaBuilder.equal(root.get(UserEntity._isActive), IsActive.Active),
criteriaBuilder.in(root.get(UserEntity._id)).value(queryUtilsService.buildSubQuery(new BuildSubQueryInput<>(new BuildSubQueryInput.Builder<>(TenantUserEntity.class, UUID.class)
.query(query)
.criteriaBuilder(criteriaBuilder)
.keyPathFunc((subQueryRoot) -> subQueryRoot.get(TenantUserEntity._userId))
.filterFunc((subQueryRoot, cb) ->
{
try {
return cb.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)
);
} catch (InvalidApplicationException e) {
throw new RuntimeException(e);
}
}
)
))
)
));
query.multiselect(root.get(UserEntity._id).alias(UserEntity._id));
List<UserEntity> results = this.entityManager.createQuery(query).getResultList();
return !results.isEmpty();
}
return false;
}
@Override
public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.tenantScope.setTenant(null, null);
this.tenantEntityManager.disableTenantFilters();
}
@Override
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
}
}

View File

@ -1,184 +0,0 @@
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.authorization.ClaimNames;
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.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() + ClaimNames.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(principal);
if (!scoped) scoped = this.scopeByClient(principal);
if (!scoped && this.tenantScope.isSet() && this.tenantScopeProperties.getEnforceTrustedTenant())
throw new MyForbiddenException(this.errorThesaurusProperties.getMissingTenant().getCode(), this.errorThesaurusProperties.getMissingTenant().getMessage());
}
}
private boolean scopeByPrincipal(MyPrincipal principal) {
String tenantCode = this.claimExtractor.tenantString(principal);
if (this.conventionService.isNullOrEmpty(tenantCode)) tenantCode = this.claimExtractor.asString(principal, this.clientTenantClaimName);
if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return false;
if (tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())){
logger.debug("parsed tenant header and set tenant to default tenant");
this.tenantScope.setTenant(null, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
return true;
}
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
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) {
logger.debug("parsed tenant header and set tenant id to {}", tenantId);
this.tenantScope.setTenant(tenantId, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
return true;
}
return false;
}
private boolean scopeByClient(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, this.tenantScope.isSet(), (this.tenantScope.isSet() ? this.tenantScope.getTenant() : null));
return isWhiteListed && this.tenantScope.isSet();
}
private UUID getTenantIdFromDatabase(String tenantCode) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.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<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
return results.getFirst().getId();
}
return null;
}
private String getTenantCodeFromDatabase(UUID tenantId) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.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<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
return results.getFirst().getCode();
}
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

@ -1,9 +0,0 @@
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

@ -1,146 +0,0 @@
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.authorization.ClaimNames;
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.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 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(@NotNull WebRequest request) {
if (!this.currentPrincipalResolver.currentPrincipal().isAuthenticated()) return;
if (!this.tenantScope.isMultitenant()) return;
String tenantCode = request.getHeader(ClaimNames.TenantClaimName);
logger.debug("retrieved request tenant header is: {}", tenantCode);
if (tenantCode == null || this.conventionService.isNullOrEmpty(tenantCode)) return;
if (tenantCode.equalsIgnoreCase(this.tenantScope.getDefaultTenantCode())){
logger.debug("parsed tenant header and set tenant to default tenant");
this.tenantScope.setTenant(null, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
return;
}
UUID tenantId = this.conventionService.parseUUIDSafe(tenantCode);
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) {
logger.debug("parsed tenant header and set tenant id to {}", tenantId);
this.tenantScope.setTenant(tenantId, tenantCode);
this.claimExtractorContext.putReplaceParameter(TenantScope.TenantReplaceParameter, tenantCode);
}
}
private UUID getTenantIdFromDatabase(String tenantCode) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.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<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
return results.getFirst().getId();
}
return null;
}
private String getTenantCodeFromDatabase(UUID tenantId) {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<TenantEntity> query = criteriaBuilder.createQuery(TenantEntity.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<TenantEntity> results = this.entityManager.createQuery(query).getResultList();
if (results.size() == 1) {
return results.getFirst().getCode();
}
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

@ -1,44 +0,0 @@
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

@ -1,12 +0,0 @@
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

@ -1,91 +0,0 @@
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) {
HashMap<String, String> keyParts = new HashMap<>();
keyParts.put("$user_id$", userId.toString().toLowerCase(Locale.ROOT));
keyParts.put("$tenant_id$", tenantId.toString().toLowerCase(Locale.ROOT));
return this.generateKey(keyParts);
}
}

View File

@ -1,81 +0,0 @@
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.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 org.jetbrains.annotations.NotNull;
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 java.util.UUID;
@Component
public class UserInterceptor implements WebRequestInterceptor {
private final UserScope userScope;
private final ClaimExtractor claimExtractor;
private final CurrentPrincipalResolver currentPrincipalResolver;
private final UserInterceptorCacheService userInterceptorCacheService;
private final QueryFactory queryFactory;
@Autowired
public UserInterceptor(
UserScope userScope,
ClaimExtractor claimExtractor,
CurrentPrincipalResolver currentPrincipalResolver,
UserInterceptorCacheService userInterceptorCacheService,
QueryFactory queryFactory) {
this.userScope = userScope;
this.currentPrincipalResolver = currentPrincipalResolver;
this.claimExtractor = claimExtractor;
this.userInterceptorCacheService = userInterceptorCacheService;
this.queryFactory = queryFactory;
}
@Override
public void preHandle(@NotNull 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.findExistingUserFromDb(subjectId);
if (userId != null) {
cacheValue = new UserInterceptorCacheService.UserInterceptorCacheValue(subjectId, userId);
this.userInterceptorCacheService.put(cacheValue);
}
}
}
this.userScope.setUserId(userId);
}
private UUID findExistingUserFromDb(String subjectId) {
UserCredentialEntity userCredential = this.queryFactory.query(UserCredentialQuery.class).disableTracking().externalIds(subjectId).firstAs(new BaseFieldSet().ensure(UserCredential._user));
if (userCredential != null) {
return userCredential.getUserId();
}
return null;
}
@Override
public void postHandle(@NonNull WebRequest request, ModelMap model) {
this.userScope.setUserId(null);
}
@Override
public void afterCompletion(@NonNull WebRequest request, Exception ex) {
}
}

View File

@ -1,10 +0,0 @@
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

@ -1,77 +0,0 @@
package gr.cite.notification.web.scope.user;
import gr.cite.notification.convention.ConventionService;
import gr.cite.notification.event.UserCredentialTouchedEvent;
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> {
private final ConventionService conventionService;
public static class UserInterceptorCacheValue {
public UserInterceptorCacheValue() {
}
public UserInterceptorCacheValue(String subjectId, UUID userId) {
this.subjectId = subjectId;
this.userId = userId;
}
public String getSubjectId() {
return subjectId;
}
public void setSubjectId(String subjectId) {
this.subjectId = subjectId;
}
private String subjectId;
private UUID userId;
public UUID getUserId() {
return userId;
}
public void setUserId(UUID userId) {
this.userId = userId;
}
}
@EventListener
public void handleTenantTouchedEvent(UserCredentialTouchedEvent event) {
if (!this.conventionService.isNullOrEmpty(event.getSubjectId()))
this.evict(this.buildKey(event.getSubjectId()));
}
@Autowired
public UserInterceptorCacheService(UserInterceptorCacheOptions options, ConventionService conventionService1) {
super(options);
this.conventionService = conventionService1;
}
@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

@ -1,21 +0,0 @@
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/notification.yml[.yml], optional:classpath:config/notification-${spring.profiles.active}.yml[.yml], optional:file:../config/notification-${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

@ -1,62 +0,0 @@
cache:
manager:
fallbackToNoOpCache: true
caffeineCaches:
- names: [ apikey ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteSeconds: 600
- names: [ tenantByCode ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteSeconds: 20
- names: [ tenantById ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteSeconds: 20
- names: [ userBySubjectId ]
allowNullValues: true
initialCapacity: 100
maximumSize: 5000
enableRecordStats: false
expireAfterWriteSeconds: 20
- names: [ userAccessTenant ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteSeconds: 20
- names: [ formattingUserProfile ]
allowNullValues: true
initialCapacity: 100
maximumSize: 500
enableRecordStats: false
expireAfterWriteSeconds: 20
mapCaches:
apiKey:
name: apikey
keyPattern: ntf_resolve_$keyhash$:v0
userBySubjectId:
name: userBySubjectId
keyPattern: ntf_user_by_subject_$subject$:v0
tenantByCode:
name: tenantByCode
keyPattern: ntf_tenant_by_code_$code$:v0
tenantById:
name: tenantById
keyPattern: ntf_tenant_by_id_$tenantId$:v0
userAllowedTenant:
name: userAccessTenant
keyPattern: ntf_user_access_tenant_$user_id$_$tenant_id$:v0
formattingUserProfileCache:
name: formattingUserProfile
keyPattern: ntf_formatting_user_profile$user_id$:v0
template:
name: template
key-pattern: ------

View File

@ -1,35 +0,0 @@
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

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

View File

@ -1,7 +0,0 @@
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

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

View File

@ -1,29 +0,0 @@
spring:
jpa:
properties:
org:
hibernate:
flushMode: MANUAL
hibernate:
globally_quoted_identifiers: true
ddl-auto: validate
dialect: org.hibernate.dialect.PostgreSQLDialect
hibernate:
naming:
physical-strategy: gr.cite.notification.data.namingstrategy.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: 10
idle-timeout: 600000
max-lifetime: 1800000
naming-strategy:
prefix: ntf_

View File

@ -1,14 +0,0 @@
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

@ -1,59 +0,0 @@
error-thesaurus:
# common errors start with 1..
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
model-validation:
code: 106
message: validation error
tenant-not-allowed:
code: 112
message: tenant not allowed
tenant-tampering:
code: 127
message: Tenant tampering
tenant-configuration-type-can-not-change:
code: 128
message: Tenant configuration type can not change
multiple-tenant-configuration-type-not-allowed:
code: 129
message: Multiple Tenant Configuration Type Not Allowed
# annotations&notification errors start with 2..
invalid-api-key:
code: 200
message: provided APIKey not valid
stale-api-key:
code: 201
message: there was a problem authorizing you with your API key. Please try again. Contact the system administrator if the problem persists
sensitive-info:
code: 202
message: you are attempting to access sensitive information. please don't do that
non-person-principal:
code: 203
message: the operation is available only to person users
blocking-consent:
code: 204
message: user consents are not sufficient to complete the operation
single-tenant-configuration-per-type-supported:
code: 205
message: a single tenant configuration entry per config type is supported
incompatible-tenant-configuration-types:
code: 206
message: the provided tenant configuration type is incompatible
missing-totp-token:
code: 207
message: totp token not provided
overlapping-tenant-configuration-notifier-list:
code: 208
message: Overlapping Tenant Configuration Notifier List

View File

@ -1,6 +0,0 @@
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

@ -1,58 +0,0 @@
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
- type: tenant_roles
filterBy: "(.*):::TenantCode::"
extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g1]]"
GlobalRoles:
- type: resource_access
path: dmp_web.roles
TenantRoles:
- type: tenant_roles
filterBy: "(.*):::TenantCode::"
extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g1]]"
Scope:
- type: scope
AccessToken:
- type: x-access-token
visibility: SENSITIVE
Tenant:
- type: x-tenant
IssuedAt:
- type: iat
Issuer:
- type: iss
Audience:
- type: aud
TokenType:
- type: typ
AuthorizedParty:
- type: azp
Authorities:
- type: authorities
TenantCodes:
- type: tenant_roles
filterBy: "(.*):(.*)"
extractByExpression: "(.*):(.*)"
extractExpressionValue: "[[g2]]"

View File

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

View File

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

View File

@ -1,35 +0,0 @@
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

@ -1,728 +0,0 @@
notification:
staticFields:
fields:
- key: "{installation-url}"
type: "String"
value: "http://localhost:4200"
resolver:
global-policies:
- #planInvitationExternalUser
type: 065DEECD-21BB-44AF-9983-E660FDF24BC4
contacts: [ email ]
- #planInvitationExistingUser
type: 4904dea2-5079-46d3-83be-3a19c9ab45dc
contacts: [ email, inapp ]
- #dpmModified
type: 4542262A-22F8-4BAA-9DB6-1C8E70AC1DBB
contacts: [ inapp, email ]
- #planFinalised
type: 90DB0B46-42DE-BD89-AEBF-6F27EFEB256E
contacts: [ inapp, email ]
- #descriptionCreated
type: 8965b1d5-99a6-4acf-9016-c0d0ce341364
contacts: [ inapp, email ]
- #descriptionModified
type: 4FDBFA80-7A71-4A69-B854-67CBB70648F1
contacts: [ inapp, email ]
- #descriptionFinalised
type: 33790bad-94d4-488a-8ee2-7f6295ca18ea
contacts: [ inapp, email ]
- #descriptionAnnotationCreated
type: db1e99d2-a240-4e75-9bb2-ef25b234c1f0
contacts: [ inapp, email ]
- #mergeAcountConfirmation
type: BFE68845-CB05-4C5A-A03D-29161A7C9660
contacts: [ email ]
- #removeCredentialConfirmation
type: C9BC3F16-057E-4BBA-8A5F-36BD835E5604
contacts: [ email ]
- #planDeposit
type: 55736F7A-83AB-4190-AF43-9D031A6F9612
contacts: [ inapp, email ]
- #descriptionTemplateInvitation
type: 223BB607-EFA1-4CE7-99EC-4BEABFEF9A8B
contacts: [ inapp, email ]
- #contactSupportType
type: 5B1D6C52-88F9-418B-9B8A-6F1F963D9EAD
contacts: [ email ]
- #publicContactSupportType
type: B542B606-ACC6-4629-ADEF-4D8EE2F01222
contacts: [ email ]
- #tenantSpecificInvitationExternalUserType
type: 497dada5-eccc-4bc0-9e0b-63e22b4eb0be
contacts: [ email ]
- #tenantSpecificInvitationExistingUserType
type: b3809c17-d1e4-420a-919c-828564114191
contacts: [ inapp, email ]
message:
email:
flows:
- #planInvitationExternalUser
key: 065DEECD-21BB-44AF-9983-E660FDF24BC4
subject-path: classpath:notification_templates/planinvitationexternaluser/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/planinvitationexternaluser/email/body.{language}.html
body-field-options:
mandatory: [ "{planname}", "{planrole}", "{installation-url}", "{confirmationToken}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{planname}]': null
'[{planrole}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #planInvitationExistingUser
key: 4904dea2-5079-46d3-83be-3a19c9ab45dc
subject-path: classpath:notification_templates/planinvitationexistinguser/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/planinvitationexistinguser/email/body.{language}.html
body-field-options:
mandatory: [ "{planname}", "{planrole}", "{reasonName}", "{installation-url}", "{id}"]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{planname}]': null
'[{planrole}]': null
'[{reasonName}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #planModified
key: 4542262A-22F8-4BAA-9DB6-1C8E70AC1DBB
subject-path: classpath:notification_templates/planmodified/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/planmodified/email/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #planFinalised
key: 90DB0B46-42DE-BD89-AEBF-6F27EFEB256E
subject-path: classpath:notification_templates/planfinalised/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/planfinalised/email/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #desriptionCreated
key: 8965b1d5-99a6-4acf-9016-c0d0ce341364
subject-path: classpath:notification_templates/descriptioncreated/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/descriptioncreated/email/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #desriptionModified
key: 4FDBFA80-7A71-4A69-B854-67CBB70648F1
subject-path: classpath:notification_templates/descriptionmodified/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/descriptionmodified/email/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #descriptionFinalised
key: 33790bad-94d4-488a-8ee2-7f6295ca18ea
subject-path: classpath:notification_templates/descriptionfinalised/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/descriptionfinalised/email/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #descriptionAnnotationCreated
key: db1e99d2-a240-4e75-9bb2-ef25b234c1f0
subject-path: classpath:notification_templates/descriptionannotationcreated/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/descriptionannotationcreated/email/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #mergeAccountConfirmation
key: BFE68845-CB05-4C5A-A03D-29161A7C9660
subject-path: classpath:notification_templates/mergeacountconfirmation/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/mergeacountconfirmation/email/body.{language}.html
body-field-options:
mandatory: [ "{userName}", "{installation-url}", "{confirmationToken}" ]
optional:
- key: "{expiration_time}"
value: ---
formatting:
'[{userName}]': null
'[{installation-url}]': null
'[{expiration_time}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #removeCredentialConfirmation
key: C9BC3F16-057E-4BBA-8A5F-36BD835E5604
subject-path: classpath:notification_templates/removecredentialconfirmation/email/subject.{language}.txt
subject-field-options:
mandatory: [ "{installation-url}", "{confirmationToken}" ]
optional: [ ]
body-path: classpath:notification_templates/removecredentialconfirmation/email/body.{language}.html
body-field-options:
mandatory: [ ]
optional:
- key: "{email}"
value: email
- key: "{expiration_time}"
value: --
formatting:
'[{email}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #planDeposit
key: 55736F7A-83AB-4190-AF43-9D031A6F9612
subject-path: classpath:notification_templates/plandeposit/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/plandeposit/email/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #descriptionTemplateInvitation
key: 223BB607-EFA1-4CE7-99EC-4BEABFEF9A8B
subject-path: classpath:notification_templates/descriptiontemplateinvitation/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/descriptiontemplateinvitation/email/body.{language}.html
body-field-options:
mandatory: [ "{templateName}", "{installation-url}", "{templateID}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{templateName}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #contactSupportType
key: 5B1D6C52-88F9-418B-9B8A-6F1F963D9EAD
subject-path: classpath:notification_templates/contactsupport/email/subject.{language}.txt
subject-field-options:
mandatory: [ "{subject}" ]
optional: [ ]
body-path: classpath:notification_templates/contactsupport/email/body.{language}.html
body-field-options:
mandatory: [ "{description}", "{email}" ]
formatting:
'[{subject}]': null
'[{description}]': null
'[{email}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #publicContactSupportType
key: B542B606-ACC6-4629-ADEF-4D8EE2F01222
subject-path: classpath:notification_templates/publiccontactsupport/email/subject.{language}.txt
subject-field-options:
mandatory: [ "{subject}" ]
optional: [ ]
body-path: classpath:notification_templates/publiccontactsupport/email/body.{language}.html
body-field-options:
mandatory: [ "{description}", "{email}", "{name}" ]
formatting:
'[{subject}]': null
'[{description}]': null
'[{email}]': null
'[{name}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #tenantSpecificInvitationExternalUserType
key: 497dada5-eccc-4bc0-9e0b-63e22b4eb0be
subject-path: classpath:notification_templates/tenantspecificinvitationexternaluser/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/tenantspecificinvitationexternaluser/email/body.{language}.html
body-field-options:
mandatory: [ "{userName}", "{installation-url}", "{confirmationToken}" ]
optional:
- key: "{expiration_time}"
value: ---
- key: "{tenantName}"
value: OpenCDMP
formatting:
'[{userName}]': null
'[{tenantName}]': null
'[{installation-url}]': null
'[{expiration_time}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #tenantSpecificInvitationExistingUserType
key: b3809c17-d1e4-420a-919c-828564114191
subject-path: classpath:notification_templates/tenantspecificinvitationexistinguser/email/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/tenantspecificinvitationexistinguser/email/body.{language}.html
body-field-options:
mandatory: [ "{userName}", "{installation-url}", "{confirmationToken}" ]
optional:
- key: "{expiration_time}"
value: ---
- key: "{tenantName}"
value: OpenCDMP
formatting:
'[{userName}]': null
'[{tenantName}]': null
'[{installation-url}]': null
'[{expiration_time}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
template-cache:
prefix: ${CACHE_DISAMBIGUATION:}
key-pattern: "{prefix}:Notification_Message_Email_Template:{key}:v0"
in-app:
flows:
- #planInvitationExistingUser
key: 4904dea2-5079-46d3-83be-3a19c9ab45dc
subject-path: classpath:notification_templates/planinvitationexistinguser/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/planinvitationexistinguser/inapp/body.{language}.html
body-field-options:
mandatory: [ "{planname}", "{planrole}", "{reasonName}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{planname}]': null
'[{planrole}]': null
'[{reasonName}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
priority-key: null
cipher-fields: [ ]
- #planModified
key: 4542262A-22F8-4BAA-9DB6-1C8E70AC1DBB
subject-path: classpath:notification_templates/planmodified/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/planmodified/inapp/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{installation-url}]': null
'[{id}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
priority-key: null
cipher-fields: [ ]
- #planFinalised
key: 90DB0B46-42DE-BD89-AEBF-6F27EFEB256E
subject-path: classpath:notification_templates/planfinalised/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/planfinalised/inapp/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{installation-url}]': null
'[{id}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
priority-key: null
cipher-fields: [ ]
- #desriptionCreated
key: 8965b1d5-99a6-4acf-9016-c0d0ce341364
subject-path: classpath:notification_templates/descriptioncreated/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/descriptioncreated/inapp/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
- #desriptionModified
key: 4FDBFA80-7A71-4A69-B854-67CBB70648F1
subject-path: classpath:notification_templates/descriptionmodified/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/descriptionmodified/inapp/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{installation-url}]': null
'[{id}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
priority-key: null
cipher-fields: [ ]
- #descriptionFinalised
key: 33790bad-94d4-488a-8ee2-7f6295ca18ea
subject-path: classpath:notification_templates/descriptionfinalised/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/descriptionfinalised/inapp/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{installation-url}]': null
'[{id}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
priority-key: null
cipher-fields: [ ]
- #descriptionAnnotationCreated
key: db1e99d2-a240-4e75-9bb2-ef25b234c1f0
subject-path: classpath:notification_templates/descriptionannotationcreated/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/descriptionannotationcreated/inapp/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{installation-url}]': null
'[{id}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
priority-key: null
cipher-fields: [ ]
- #mergeAccountConfirmation
key: BFE68845-CB05-4C5A-A03D-29161A7C9660
subject-path: classpath:notification_templates/mergeacountconfirmation/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/mergeacountconfirmation/inapp/body.{language}.html
body-field-options:
mandatory: [ "{userName}", "{installation-url}", "{confirmationToken}" ]
optional:
- key: "{expiration_time}"
value: ---
- key: "{tenant-url-path}"
value:
formatting:
'[{userName}]': null
'[{installation-url}]': null
'[{confirmationToken}]': null
'[{expiration_time}]': null
'[{tenant-url-path}]': null
priority-key: null
cipher-fields: [ ]
- #removeCredentialConfirmation
key: C9BC3F16-057E-4BBA-8A5F-36BD835E5604
subject-path: classpath:notification_templates/removecredentialconfirmation/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ "{installation-url}", "{confirmationToken}" ]
optional: [ ]
body-path: classpath:notification_templates/removecredentialconfirmation/inapp/body.{language}.html
body-field-options:
mandatory: [ ]
optional:
- key: "{email}"
value: email
- key: "{expiration_time}"
value: --
- key: "{tenant-url-path}"
value:
formatting:
'[{email}]': null
'[{expiration_time}]': null
'[{tenant-url-path}]': null
priority-key: null
cipher-fields: [ ]
- #planDeposit
key: 55736F7A-83AB-4190-AF43-9D031A6F9612
subject-path: classpath:notification_templates/plandeposit/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/plandeposit/inapp/body.{language}.html
body-field-options:
mandatory: [ "{reasonName}", "{name}", "{installation-url}", "{id}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{reasonName}]': null
'[{name}]': null
'[{installation-url}]': null
'[{id}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
priority-key: null
cipher-fields: [ ]
- #descriptionTemplateInvitation
key: 223BB607-EFA1-4CE7-99EC-4BEABFEF9A8B
subject-path: classpath:notification_templates/descriptiontemplateinvitation/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/descriptiontemplateinvitation/inapp/body.{language}.html
body-field-options:
mandatory: [ "{templateName}", "{installation-url}", "{templateID}" ]
optional:
- key: "{recipient}"
value:
- key: "{tenant-url-path}"
value:
formatting:
'[{templateName}]': null
'[{installation-url}]': null
'[{templateID}]': null
'[{recipient}]': null
'[{tenant-url-path}]': null
priority-key: null
cipher-fields: [ ]
- #tenantSpecificInvitationExistingUserType
key: b3809c17-d1e4-420a-919c-828564114191
subject-path: classpath:notification_templates/tenantspecificinvitationexistinguser/inapp/subject.{language}.txt
subject-field-options:
mandatory: [ ]
optional: [ ]
body-path: classpath:notification_templates/tenantspecificinvitationexistinguser/inapp/body.{language}.html
body-field-options:
mandatory: [ "{userName}", "{installation-url}", "{confirmationToken}" ]
optional:
- key: "{expiration_time}"
value: ---
- key: "{tenantName}"
value: OpenCDMP
formatting:
'[{userName}]': null
'[{tenantName}]': null
'[{installation-url}]': null
'[{expiration_time}]': null
cc: [ ]
cc-mode: 0
bcc: [ ]
bcc-mode: 0
allow-attachments: false
cipher-fields: [ ]
template-cache:
prefix: ${CACHE_DISAMBIGUATION}
key-pattern: "{prefix}:Notification_Message_InApp_Template:{key}:v0"

View File

@ -1,17 +0,0 @@
notification:
task:
processor:
enable: true
interval-seconds: 5
options:
retry-threshold: 300
max-retry-delay-seconds: 10800
too-old-to-send-seconds: 36000
too-old-to-track-seconds: 604800
overrides: []
ad-hoc-config:
ad-hoc-notification-type: null
override-cache:
template-cache:
prefix: ${CACHE_DISAMBIGUATION:}
key-pattern: "{prefix}:Notification_Override_Template:{tenant}:{type}:{channel}:{{language}}:v0"

View File

@ -1,193 +0,0 @@
permissions:
extendedClaims: [ ]
policies:
# Tenants
BrowseTenant:
roles: []
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EditTenant:
roles: []
clients: [ "opencdmp-api-dev" ]
allowAnonymous: false
allowAuthenticated: false
DeleteTenant:
roles: []
claims: [ ]
clients: [ "opencdmp-api-dev" ]
allowAnonymous: false
allowAuthenticated: false
AllowNoTenant:
roles:
- Admin
- InstallationAdmin
claims: [ ]
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# Users
BrowseUser:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EditUser:
roles: []
clients: [ "opencdmp-api-dev" ]
allowAnonymous: false
allowAuthenticated: false
DeleteUser:
roles: []
claims: [ ]
clients: [ "opencdmp-api-dev" ]
allowAnonymous: false
allowAuthenticated: false
# UserContactInfo
BrowseUserContactInfo:
roles:
- Admin
- InstallationAdmin
clients: [ "opencdmp-api-dev" ]
allowAnonymous: false
allowAuthenticated: false
EditUserContactInfo:
roles:
- Admin
- InstallationAdmin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
DeleteUserContactInfo:
roles:
- Admin
- InstallationAdmin
claims: [ ]
clients: [ "opencdmp-api-dev" ]
allowAnonymous: false
allowAuthenticated: false
#Notification
BrowseNotification:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EditNotification:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
DeleteNotification:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# TenantConfiguration
BrowseTenantConfiguration:
roles:
- Admin
- TenantAdmin
claims: [ ]
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EditTenantConfiguration:
roles:
- Admin
- TenantAdmin
clients: [ "opencdmp-api-dev" ]
allowAnonymous: false
allowAuthenticated: false
DeleteTenantConfiguration:
roles:
- Admin
- TenantAdmin
clients: [ "opencdmp-api-dev" ]
allowAnonymous: false
allowAuthenticated: false
#User Notification Preference
BrowseUserNotificationPreference:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EditUserNotificationPreference:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# ViewPage Permissions
ViewNotificationPage:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
ViewNotificationEventRulePage:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
ViewInAppNotificationPage:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
ViewNotificationTemplatePage:
roles:
- Admin
- TenantAdmin
- TenantConfigManager
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# Notification Template Permissions
BrowseNotificationTemplate:
roles:
- Admin
- TenantAdmin
- TenantConfigManager
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
EditNotificationTemplate:
roles:
- Admin
- TenantAdmin
- TenantConfigManager
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
DeleteNotificationTemplate:
roles:
- Admin
- TenantAdmin
- TenantConfigManager
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
# In App Notification Permissions
BrowseInAppNotification:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false
DeleteInAppNotification:
roles:
- Admin
clients: [ ]
allowAnonymous: false
allowAuthenticated: false

View File

@ -1,21 +0,0 @@
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

@ -1,55 +0,0 @@
spring:
rabbitmq:
host: ${RABBIT_HOST}
port: ${RABBIT_PORT}
username: ${RABBIT_USER}
password: ${RABBIT_PASS}
ssl:
enabled: false
queue:
rabbitmq:
enable: false
appId: ${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
rabbitmq:
enable: false
interval-seconds: 5
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
tenant-default-locale-removal-topic: tenant_default_locale.remove
tenant-default-locale-touched-topic: tenant_default_locale.touch
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: 5
options:
retry-threashold: 100
retry-delay-step-seconds: 300
max-retry-delay-seconds: 10800
too-old-to-send-seconds: 604800

View File

@ -1,6 +0,0 @@
web:
security:
idp:
resource:
jwt:
audiences: [ "dmp_notification" ]

View File

@ -1,14 +0,0 @@
web:
security:
enabled: true
authorized-endpoints: [ api ]
allowed-endpoints: [ public ]
idp:
api-key:
enabled: false
resource:
token-type: JWT #| opaque
jwt:
claims: [ role, x-role ]
issuer-uri: ${IDP_ISSUER_URI}
validIssuer: ${IDP_ISSUER_URI}

View File

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

View File

@ -1,13 +0,0 @@
server:
port: ${WEB_PORT}
forward-headers-strategy: NONE
tomcat:
threads:
max: 20
max-connections: 10000
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB

View File

@ -1,7 +0,0 @@
tenant:
multitenancy:
is-multitenant: true
default-tenant-code: default
interceptor:
client-claims-prefix: client_
enforce-trusted-tenant: false

View File

@ -1,7 +0,0 @@
tenant:
multitenancy:
is-multitenant: false
interceptor:
white-listed-clients: [ ]
enforce-trusted-tenant: false
white-listed-endpoints: [ '/api/notification/principal/me' ]

View File

@ -1,57 +0,0 @@
<?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="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

@ -1,10 +0,0 @@
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

@ -1,6 +0,0 @@
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

@ -1,14 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>OpenCDMP Notification</title>
</head>
<body class="">
{description}
<br/>
<br/>
Send by user: {email}
</body>
</html>

View File

@ -1,304 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>OpenCDMP Notification</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%; }
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; }
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top; }
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%; }
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px; }
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px; }
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%; }
.wrapper {
box-sizing: border-box;
padding: 20px; }
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%; }
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center; }
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px; }
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize; }
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px; }
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px; }
a {
color: #3498db;
text-decoration: underline; }
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto; }
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center; }
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize; }
.btn-primary table td {
background-color: #3498db; }
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff; }
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0; }
.first {
margin-top: 0; }
.align-center {
text-align: center; }
.align-right {
text-align: right; }
.align-left {
text-align: left; }
.clear {
clear: both; }
.mt0 {
margin-top: 0; }
.mb0 {
margin-bottom: 0; }
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0; }
.powered-by a {
text-decoration: none; }
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0; }
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important; }
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important; }
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important; }
table[class=body] .content {
padding: 0 !important; }
table[class=body] .container {
padding: 0 !important;
width: 100% !important; }
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important; }
table[class=body] .btn table {
width: 100% !important; }
table[class=body] .btn a {
width: 100% !important; }
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important; }}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%; }
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%; }
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important; }
.btn-primary table td:hover {
background-color: #34495e !important; }
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important; } }
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p>Dear {recipient},</p>
<p>{reasonName} made a comment on the Description {name}.</p>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{installation-url}{tenant-url-path}/descriptions/edit/{id}" target="_blank">Click here to view it.</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -1,12 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body class="">
<p>Dear {recipient},</p>
<p>{reasonName} made a comment on the Description {name}.</p>
<a href="{installation-url}{tenant-url-path}/descriptions/edit/{id}" target="_blank">Click here to view it.</a>
</body>
</html>

View File

@ -1,304 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>OpenCDMP Notification</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%; }
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; }
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top; }
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%; }
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px; }
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px; }
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%; }
.wrapper {
box-sizing: border-box;
padding: 20px; }
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%; }
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center; }
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px; }
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize; }
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px; }
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px; }
a {
color: #3498db;
text-decoration: underline; }
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto; }
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center; }
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize; }
.btn-primary table td {
background-color: #3498db; }
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff; }
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0; }
.first {
margin-top: 0; }
.align-center {
text-align: center; }
.align-right {
text-align: right; }
.align-left {
text-align: left; }
.clear {
clear: both; }
.mt0 {
margin-top: 0; }
.mb0 {
margin-bottom: 0; }
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0; }
.powered-by a {
text-decoration: none; }
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0; }
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important; }
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important; }
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important; }
table[class=body] .content {
padding: 0 !important; }
table[class=body] .container {
padding: 0 !important;
width: 100% !important; }
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important; }
table[class=body] .btn table {
width: 100% !important; }
table[class=body] .btn a {
width: 100% !important; }
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important; }}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%; }
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%; }
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important; }
.btn-primary table td:hover {
background-color: #34495e !important; }
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important; } }
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p>Dear {recipient},</p>
<p>{reasonName} created new Description with name {name}.</p>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{installation-url}{tenant-url-path}/descriptions/overview/{id}" target="_blank">Click here to view it.</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -1,12 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body class="">
<p>Dear {recipient},</p>
<p>{reasonName} created new Description with name {name}.</p>
<a href="{installation-url}{tenant-url-path}/descriptions/overview/{id}" target="_blank">Click here to view it.</a>
</body>
</html>

View File

@ -1,304 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>OpenCDMP Notification</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%; }
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; }
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top; }
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%; }
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px; }
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px; }
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%; }
.wrapper {
box-sizing: border-box;
padding: 20px; }
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%; }
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center; }
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px; }
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize; }
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px; }
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px; }
a {
color: #3498db;
text-decoration: underline; }
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto; }
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center; }
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize; }
.btn-primary table td {
background-color: #3498db; }
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff; }
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0; }
.first {
margin-top: 0; }
.align-center {
text-align: center; }
.align-right {
text-align: right; }
.align-left {
text-align: left; }
.clear {
clear: both; }
.mt0 {
margin-top: 0; }
.mb0 {
margin-bottom: 0; }
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0; }
.powered-by a {
text-decoration: none; }
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0; }
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important; }
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important; }
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important; }
table[class=body] .content {
padding: 0 !important; }
table[class=body] .container {
padding: 0 !important;
width: 100% !important; }
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important; }
table[class=body] .btn table {
width: 100% !important; }
table[class=body] .btn a {
width: 100% !important; }
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important; }}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%; }
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%; }
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important; }
.btn-primary table td:hover {
background-color: #34495e !important; }
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important; } }
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p>Dear {recipient},</p>
<p>{reasonName} just finalised the Description {name}.</p>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{installation-url}{tenant-url-path}/descriptions/overview/{id}" target="_blank">Click here to view it.</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -1,12 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body class="">
<p>Dear {recipient},</p>
<p>{reasonName} just finalised the Description {name}.</p>
<a href="{installation-url}{tenant-url-path}/descriptions/overview/{id}" target="_blank">Click here to view it.</a>
</body>
</html>

View File

@ -1,304 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>OpenCDMP Notification</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%; }
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; }
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top; }
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%; }
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px; }
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px; }
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%; }
.wrapper {
box-sizing: border-box;
padding: 20px; }
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%; }
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center; }
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px; }
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize; }
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px; }
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px; }
a {
color: #3498db;
text-decoration: underline; }
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto; }
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center; }
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize; }
.btn-primary table td {
background-color: #3498db; }
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff; }
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0; }
.first {
margin-top: 0; }
.align-center {
text-align: center; }
.align-right {
text-align: right; }
.align-left {
text-align: left; }
.clear {
clear: both; }
.mt0 {
margin-top: 0; }
.mb0 {
margin-bottom: 0; }
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0; }
.powered-by a {
text-decoration: none; }
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0; }
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important; }
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important; }
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important; }
table[class=body] .content {
padding: 0 !important; }
table[class=body] .container {
padding: 0 !important;
width: 100% !important; }
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important; }
table[class=body] .btn table {
width: 100% !important; }
table[class=body] .btn a {
width: 100% !important; }
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important; }}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%; }
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%; }
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important; }
.btn-primary table td:hover {
background-color: #34495e !important; }
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important; } }
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p>Dear {recipient},</p>
<p>{reasonName} just made changes to the Description {name}.</p>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{installation-url}{tenant-url-path}/descriptions/overview/{id}" target="_blank">Click here to view it.</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -1,12 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body class="">
<p>Dear {recipient},</p>
<p>{reasonName} just made changes to the Description {name}.</p>
<a href="{installation-url}{tenant-url-path}/descriptions/overview/{id}" target="_blank">Click here to view it.</a>
</body>
</html>

View File

@ -1,305 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>OpenCDMP Notification</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%; }
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; }
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top; }
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%; }
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px; }
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px; }
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%; }
.wrapper {
box-sizing: border-box;
padding: 20px; }
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%; }
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center; }
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px; }
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize; }
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px; }
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px; }
a {
color: #3498db;
text-decoration: underline; }
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto; }
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center; }
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize; }
.btn-primary table td {
background-color: #3498db; }
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff; }
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0; }
.first {
margin-top: 0; }
.align-center {
text-align: center; }
.align-right {
text-align: right; }
.align-left {
text-align: left; }
.clear {
clear: both; }
.mt0 {
margin-top: 0; }
.mb0 {
margin-bottom: 0; }
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0; }
.powered-by a {
text-decoration: none; }
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0; }
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important; }
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important; }
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important; }
table[class=body] .content {
padding: 0 !important; }
table[class=body] .container {
padding: 0 !important;
width: 100% !important; }
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important; }
table[class=body] .btn table {
width: 100% !important; }
table[class=body] .btn a {
width: 100% !important; }
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important; }}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%; }
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%; }
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important; }
.btn-primary table td:hover {
background-color: #34495e !important; }
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important; } }
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p>Dear {recipient},</p>
<p>You have been invited to co-develop the Template {templateName}.</p>
<p>Click the button to redirect to {templateName}.</p>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{installation-url}{tenant-url-path}/description-templates/{templateID}" target="_blank">{templateName}</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -1,13 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body class="">
<p>Dear {recipient},</p>
<p>You have been invited to co-develop the Template {templateName}.</p>
<p>Click the button to redirect to {templateName}.</p>
<a href="{installation-url}{tenant-url-path}/description-templates/{templateID}" target="_blank">{templateName}</a>
</body>
</html>

View File

@ -1,304 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>OpenCDMP Notification</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%; }
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; }
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top; }
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%; }
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px; }
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px; }
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%; }
.wrapper {
box-sizing: border-box;
padding: 20px; }
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%; }
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center; }
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px; }
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize; }
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px; }
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px; }
a {
color: #3498db;
text-decoration: underline; }
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto; }
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center; }
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize; }
.btn-primary table td {
background-color: #3498db; }
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff; }
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0; }
.first {
margin-top: 0; }
.align-center {
text-align: center; }
.align-right {
text-align: right; }
.align-left {
text-align: left; }
.clear {
clear: both; }
.mt0 {
margin-top: 0; }
.mb0 {
margin-bottom: 0; }
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0; }
.powered-by a {
text-decoration: none; }
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0; }
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important; }
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important; }
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important; }
table[class=body] .content {
padding: 0 !important; }
table[class=body] .container {
padding: 0 !important;
width: 100% !important; }
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important; }
table[class=body] .btn table {
width: 100% !important; }
table[class=body] .btn a {
width: 100% !important; }
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important; }}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%; }
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%; }
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important; }
.btn-primary table td:hover {
background-color: #34495e !important; }
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important; } }
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p>Dear {recipient},</p>
<p>{reasonName} just publish the {name}.</p>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{installation-url}{tenant-url-path}/plans/overview/{id}" target="_blank">Click here to view it.</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -1,12 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body class="">
<p>Dear {recipient},</p>
<p>{reasonName} just publish the {name}.</p>
<a href="{installation-url}{tenant-url-path}/plans/overview/{id}" target="_blank">Click here to view it.</a>
</body>
</html>

View File

@ -1,304 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>OpenCDMP Notification</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%; }
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; }
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top; }
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%; }
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px; }
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px; }
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%; }
.wrapper {
box-sizing: border-box;
padding: 20px; }
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%; }
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center; }
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px; }
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize; }
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px; }
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px; }
a {
color: #3498db;
text-decoration: underline; }
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto; }
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center; }
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize; }
.btn-primary table td {
background-color: #3498db; }
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff; }
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0; }
.first {
margin-top: 0; }
.align-center {
text-align: center; }
.align-right {
text-align: right; }
.align-left {
text-align: left; }
.clear {
clear: both; }
.mt0 {
margin-top: 0; }
.mb0 {
margin-bottom: 0; }
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0; }
.powered-by a {
text-decoration: none; }
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0; }
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important; }
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important; }
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important; }
table[class=body] .content {
padding: 0 !important; }
table[class=body] .container {
padding: 0 !important;
width: 100% !important; }
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important; }
table[class=body] .btn table {
width: 100% !important; }
table[class=body] .btn a {
width: 100% !important; }
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important; }}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%; }
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%; }
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important; }
.btn-primary table td:hover {
background-color: #34495e !important; }
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important; } }
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p>Dear {recipient},</p>
<p>{reasonName} just finalised the Plan {name}.</p>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{installation-url}{tenant-url-path}/plans/overview/{id}" target="_blank">Click here to view it.</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -1,12 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body class="">
<p>Dear {recipient},</p>
<p>{reasonName} just finalised the Plan {name}.</p>
<a href="{installation-url}{tenant-url-path}/plans/overview/{id}" target="_blank">Click here to view it.</a>
</body>
</html>

View File

@ -1,304 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>OpenCDMP Notification</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%; }
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; }
table {
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%; }
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top; }
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%; }
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px; }
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px; }
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%; }
.wrapper {
box-sizing: border-box;
padding: 20px; }
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%; }
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center; }
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px; }
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize; }
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px; }
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px; }
a {
color: #3498db;
text-decoration: underline; }
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%; }
.btn > tbody > tr > td {
padding-bottom: 15px; }
.btn table {
width: auto; }
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center; }
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize; }
.btn-primary table td {
background-color: #3498db; }
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff; }
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0; }
.first {
margin-top: 0; }
.align-center {
text-align: center; }
.align-right {
text-align: right; }
.align-left {
text-align: left; }
.clear {
clear: both; }
.mt0 {
margin-top: 0; }
.mb0 {
margin-bottom: 0; }
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0; }
.powered-by a {
text-decoration: none; }
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0; }
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important; }
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important; }
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important; }
table[class=body] .content {
padding: 0 !important; }
table[class=body] .container {
padding: 0 !important;
width: 100% !important; }
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important; }
table[class=body] .btn table {
width: 100% !important; }
table[class=body] .btn a {
width: 100% !important; }
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important; }}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%; }
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%; }
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important; }
.btn-primary table td:hover {
background-color: #34495e !important; }
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important; } }
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader">This is preheader text. Some clients will show this text as a preview.</span>
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p>Dear {recipient},</p>
<p>{reasonName} just add you to collaborate to the Plan {planname} with role {planrole}.</p>
<p>Click the button to redirect to {planname}.</p>
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
<tbody>
<tr>
<td align="left">
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td> <a href="{installation-url}{tenant-url-path}/plans/overview/{id}" target="_blank">Join</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -1,14 +0,0 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>OpenCDMP Notification</title>
</head>
<body class="">
<p>Dear {recipient},</p>
<p>{reasonName} just added you to collaborate to the Plan {planname} with role {planrole}.</p>
<p>Click the button to redirect to {planname}.</p>
<a href="{installation-url}{tenant-url-path}/plans/overview/{id}" target="_blank">Join</a>
</body>
</html>

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