Initial version of sasiedzi generated by generator-jhipster@8.7.2
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: sasiedzi
|
||||
services:
|
||||
app:
|
||||
image: sasiedzi
|
||||
environment:
|
||||
- _JAVA_OPTIONS=-Xmx512m -Xms256m
|
||||
- SPRING_PROFILES_ACTIVE=prod,api-docs
|
||||
- MANAGEMENT_PROMETHEUS_METRICS_EXPORT_ENABLED=true
|
||||
- SPRING_DATASOURCE_URL=jdbc:postgresql://postgresql:5432/sasiedzi
|
||||
- SPRING_LIQUIBASE_URL=jdbc:postgresql://postgresql:5432/sasiedzi
|
||||
- SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=http://keycloak:9080/realms/jhipster
|
||||
- SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID=web_app
|
||||
- SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET=web_app
|
||||
ports:
|
||||
- 127.0.0.1:8080:8080
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- curl
|
||||
- -f
|
||||
- http://localhost:8080/management/health
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 40
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
keycloak:
|
||||
condition: service_healthy
|
||||
postgresql:
|
||||
extends:
|
||||
file: ./postgresql.yml
|
||||
service: postgresql
|
||||
keycloak:
|
||||
extends:
|
||||
file: ./keycloak.yml
|
||||
service: keycloak
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'Prometheus'
|
||||
orgId: 1
|
||||
folder: ''
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: true
|
||||
options:
|
||||
path: /etc/grafana/provisioning/dashboards
|
||||
@@ -0,0 +1,50 @@
|
||||
apiVersion: 1
|
||||
|
||||
# list of datasources that should be deleted from the database
|
||||
deleteDatasources:
|
||||
- name: Prometheus
|
||||
orgId: 1
|
||||
|
||||
# list of datasources to insert/update depending
|
||||
# whats available in the database
|
||||
datasources:
|
||||
# <string, required> name of the datasource. Required
|
||||
- name: Prometheus
|
||||
# <string, required> datasource type. Required
|
||||
type: prometheus
|
||||
# <string, required> access mode. direct or proxy. Required
|
||||
access: proxy
|
||||
# <int> org id. will default to orgId 1 if not specified
|
||||
orgId: 1
|
||||
# <string> url
|
||||
# On MacOS, replace localhost by host.docker.internal
|
||||
url: http://localhost:9090
|
||||
# <string> database password, if used
|
||||
password:
|
||||
# <string> database user, if used
|
||||
user:
|
||||
# <string> database name, if used
|
||||
database:
|
||||
# <bool> enable/disable basic auth
|
||||
basicAuth: false
|
||||
# <string> basic auth username
|
||||
basicAuthUser: admin
|
||||
# <string> basic auth password
|
||||
basicAuthPassword: admin
|
||||
# <bool> enable/disable with credentials headers
|
||||
withCredentials:
|
||||
# <bool> mark as default datasource. Max one per org
|
||||
isDefault: true
|
||||
# <map> fields that will be converted to json and stored in json_data
|
||||
jsonData:
|
||||
graphiteVersion: '1.1'
|
||||
tlsAuth: false
|
||||
tlsAuthWithCACert: false
|
||||
# <string> json object of data that will be encrypted.
|
||||
secureJsonData:
|
||||
tlsCACert: '...'
|
||||
tlsClientCert: '...'
|
||||
tlsClientKey: '...'
|
||||
version: 1
|
||||
# <bool> allow users to edit datasources from the UI.
|
||||
editable: true
|
||||
@@ -0,0 +1,52 @@
|
||||
## How to use JHCC docker compose
|
||||
# To allow JHCC to reach JHipster application from a docker container note that we set the host as host.docker.internal
|
||||
# To reach the application from a browser, you need to add '127.0.0.1 host.docker.internal' to your hosts file.
|
||||
### Discovery mode
|
||||
# JHCC support 3 kinds of discovery mode: Consul, Eureka and static
|
||||
# In order to use one, please set SPRING_PROFILES_ACTIVE to one (and only one) of this values: consul,eureka,static
|
||||
### Discovery properties
|
||||
# According to the discovery mode choose as Spring profile, you have to set the right properties
|
||||
# please note that current properties are set to run JHCC with default values, personalize them if needed
|
||||
# and remove those from other modes. You can only have one mode active.
|
||||
#### Eureka
|
||||
# - EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://admin:admin@host.docker.internal:8761/eureka/
|
||||
#### Consul
|
||||
# - SPRING_CLOUD_CONSUL_HOST=host.docker.internal
|
||||
# - SPRING_CLOUD_CONSUL_PORT=8500
|
||||
#### Static
|
||||
# Add instances to "MyApp"
|
||||
# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_0_URI=http://host.docker.internal:8081
|
||||
# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_1_URI=http://host.docker.internal:8082
|
||||
# Or add a new application named MyNewApp
|
||||
# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYNEWAPP_0_URI=http://host.docker.internal:8080
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
|
||||
#### IMPORTANT
|
||||
# If you choose Consul or Eureka mode:
|
||||
# Do not forget to remove the prefix "127.0.0.1" in front of their port in order to expose them.
|
||||
# This is required because JHCC need to communicate with Consul or Eureka.
|
||||
# - In Consul mode, the ports are in the consul.yml file.
|
||||
# - In Eureka mode, the ports are in the jhipster-registry.yml file.
|
||||
|
||||
name: sasiedzi
|
||||
services:
|
||||
jhipster-control-center:
|
||||
image: 'jhipster/jhipster-control-center:v0.5.0'
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
# Patch /etc/hosts to support resolving host.docker.internal to the internal IP address used by the host in all OSes
|
||||
- echo "`ip route | grep default | cut -d ' ' -f3` host.docker.internal" | tee -a /etc/hosts > /dev/null && java -jar /jhipster-control-center.jar
|
||||
environment:
|
||||
- _JAVA_OPTIONS=-Xmx512m -Xms256m
|
||||
- SPRING_PROFILES_ACTIVE=prod,api-docs,static,oauth2
|
||||
# For keycloak to work, you need to add '127.0.0.1 keycloak' to your hosts file
|
||||
- SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=http://keycloak:9080/realms/jhipster
|
||||
- SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID=jhipster-control-center
|
||||
- SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET=jhipster-control-center
|
||||
- SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_SASIEDZI_0_URI=http://host.docker.internal:8080
|
||||
- LOGGING_FILE_NAME=/tmp/jhipster-control-center.log
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:7419:7419
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP}
|
||||
|
||||
# usage: file_env VAR [DEFAULT]
|
||||
# ie: file_env 'XYZ_DB_PASSWORD' 'example'
|
||||
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
|
||||
# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
|
||||
file_env() {
|
||||
local var="$1"
|
||||
local fileVar="${var}_FILE"
|
||||
local def="${2:-}"
|
||||
if [[ ${!var:-} && ${!fileVar:-} ]]; then
|
||||
echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
|
||||
exit 1
|
||||
fi
|
||||
local val="$def"
|
||||
if [[ ${!var:-} ]]; then
|
||||
val="${!var}"
|
||||
elif [[ ${!fileVar:-} ]]; then
|
||||
val="$(< "${!fileVar}")"
|
||||
fi
|
||||
|
||||
if [[ -n $val ]]; then
|
||||
export "$var"="$val"
|
||||
fi
|
||||
|
||||
unset "$fileVar"
|
||||
}
|
||||
|
||||
file_env 'SPRING_DATASOURCE_URL'
|
||||
file_env 'SPRING_DATASOURCE_USERNAME'
|
||||
file_env 'SPRING_DATASOURCE_PASSWORD'
|
||||
file_env 'SPRING_LIQUIBASE_URL'
|
||||
file_env 'SPRING_LIQUIBASE_USER'
|
||||
file_env 'SPRING_LIQUIBASE_PASSWORD'
|
||||
file_env 'JHIPSTER_REGISTRY_PASSWORD'
|
||||
|
||||
exec java ${JAVA_OPTS} -noverify -XX:+AlwaysPreTouch -Djava.security.egd=file:/dev/./urandom -cp /app/resources/:/app/classes/:/app/libs/* "com.sasiedzi.event.SasiedziApp" "$@"
|
||||
@@ -0,0 +1,32 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: sasiedzi
|
||||
services:
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:25.0.1
|
||||
command: 'start-dev --import-realm'
|
||||
volumes:
|
||||
- ./realm-config:/opt/keycloak/data/import
|
||||
- ./realm-config/keycloak-health-check.sh:/opt/keycloak/health-check.sh
|
||||
environment:
|
||||
- KC_DB=dev-file
|
||||
- KEYCLOAK_ADMIN=admin
|
||||
- KEYCLOAK_ADMIN_PASSWORD=admin
|
||||
- KC_FEATURES=scripts
|
||||
- KC_HTTP_PORT=9080
|
||||
- KC_HTTPS_PORT=9443
|
||||
- KC_HEALTH_ENABLED=true
|
||||
- KC_HTTP_MANAGEMENT_PORT=9990
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:9080:9080
|
||||
- 127.0.0.1:9443:9443
|
||||
healthcheck:
|
||||
test: 'bash /opt/keycloak/health-check.sh'
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
# Increased retries due to slow Keycloak startup in GitHub Actions using MacOS
|
||||
retries: 50
|
||||
start_period: 10s
|
||||
labels:
|
||||
org.springframework.boot.ignore: true
|
||||
@@ -0,0 +1,31 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: sasiedzi
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus:v2.55.0
|
||||
volumes:
|
||||
- ./prometheus/:/etc/prometheus/
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:9090:9090
|
||||
# On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and
|
||||
# grafana/provisioning/datasources/datasource.yml
|
||||
network_mode: 'host' # to test locally running service
|
||||
grafana:
|
||||
image: grafana/grafana:11.3.0
|
||||
volumes:
|
||||
- ./grafana/provisioning/:/etc/grafana/provisioning/
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
- GF_USERS_ALLOW_SIGN_UP=false
|
||||
- GF_INSTALL_PLUGINS=grafana-piechart-panel
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:3000:3000
|
||||
# On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and
|
||||
# grafana/provisioning/datasources/datasource.yml
|
||||
network_mode: 'host' # to test locally running service
|
||||
@@ -0,0 +1,20 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: sasiedzi
|
||||
services:
|
||||
postgresql:
|
||||
image: postgres:17.0
|
||||
# volumes:
|
||||
# - ~/volumes/jhipster/sasiedzi/postgresql/:/var/lib/postgresql/data/
|
||||
environment:
|
||||
- POSTGRES_USER=sasiedzi
|
||||
- POSTGRES_PASSWORD=password
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER}']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:5432:5432
|
||||
@@ -0,0 +1,31 @@
|
||||
# Sample global config for monitoring JHipster applications
|
||||
global:
|
||||
scrape_interval: 15s # By default, scrape targets every 15 seconds.
|
||||
evaluation_interval: 15s # By default, scrape targets every 15 seconds.
|
||||
# scrape_timeout is set to the global default (10s).
|
||||
|
||||
# Attach these labels to any time series or alerts when communicating with
|
||||
# external systems (federation, remote storage, Alertmanager).
|
||||
external_labels:
|
||||
monitor: 'jhipster'
|
||||
|
||||
# A scrape configuration containing exactly one endpoint to scrape:
|
||||
# Here it's Prometheus itself.
|
||||
scrape_configs:
|
||||
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
|
||||
- job_name: 'prometheus'
|
||||
|
||||
# Override the global default and scrape targets from this job every 5 seconds.
|
||||
scrape_interval: 5s
|
||||
|
||||
# scheme defaults to 'http' enable https in case your application is server via https
|
||||
#scheme: https
|
||||
# basic auth is not needed by default. See https://www.jhipster.tech/monitoring/#configuring-metrics-forwarding for details
|
||||
#basic_auth:
|
||||
# username: admin
|
||||
# password: admin
|
||||
metrics_path: /management/prometheus
|
||||
static_configs:
|
||||
- targets:
|
||||
# On MacOS, replace localhost by host.docker.internal
|
||||
- localhost:8080
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
exec 3<>/dev/tcp/localhost/9990
|
||||
|
||||
echo -e "GET /health/ready HTTP/1.1\nhost: localhost:9990\n" >&3
|
||||
|
||||
timeout --preserve-status 1 cat <&3 | grep -m 1 status | grep -m 1 UP
|
||||
ERROR=$?
|
||||
|
||||
exec 3<&-
|
||||
exec 3>&-
|
||||
|
||||
exit $ERROR
|
||||
@@ -0,0 +1,11 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: sasiedzi
|
||||
services:
|
||||
postgresql:
|
||||
extends:
|
||||
file: ./postgresql.yml
|
||||
service: postgresql
|
||||
keycloak:
|
||||
extends:
|
||||
file: ./keycloak.yml
|
||||
service: keycloak
|
||||
@@ -0,0 +1,15 @@
|
||||
# This configuration is intended for development purpose, it's **your** responsibility to harden it for production
|
||||
name: sasiedzi
|
||||
services:
|
||||
sonar:
|
||||
container_name: sonarqube
|
||||
image: sonarqube:10.7.0-community
|
||||
# Forced authentication redirect for UI is turned off for out of the box experience while trying out SonarQube
|
||||
# For real use cases delete SONAR_FORCEAUTHENTICATION variable or set SONAR_FORCEAUTHENTICATION=true
|
||||
environment:
|
||||
- SONAR_FORCEAUTHENTICATION=false
|
||||
# If you want to expose these ports outside your dev PC,
|
||||
# remove the "127.0.0.1:" prefix
|
||||
ports:
|
||||
- 127.0.0.1:9001:9000
|
||||
- 127.0.0.1:9000:9000
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.sasiedzi.event;
|
||||
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
import tech.jhipster.config.DefaultProfileUtil;
|
||||
|
||||
/**
|
||||
* This is a helper Java class that provides an alternative to creating a {@code web.xml}.
|
||||
* This will be invoked only when the application is deployed to a Servlet container like Tomcat, JBoss etc.
|
||||
*/
|
||||
public class ApplicationWebXml extends SpringBootServletInitializer {
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
// set a default to use when no profile is configured.
|
||||
DefaultProfileUtil.addDefaultProfile(application.application());
|
||||
return application.sources(SasiedziApp.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.sasiedzi.event;
|
||||
|
||||
import jakarta.annotation.Generated;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Generated(value = "JHipster", comments = "Generated by JHipster 8.7.2")
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ ElementType.TYPE })
|
||||
public @interface GeneratedByJHipster {
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.sasiedzi.event;
|
||||
|
||||
import com.sasiedzi.event.config.ApplicationProperties;
|
||||
import com.sasiedzi.event.config.CRLFLogConverter;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.core.env.Environment;
|
||||
import tech.jhipster.config.DefaultProfileUtil;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class })
|
||||
public class SasiedziApp {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SasiedziApp.class);
|
||||
|
||||
private final Environment env;
|
||||
|
||||
public SasiedziApp(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes sasiedzi.
|
||||
* <p>
|
||||
* Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile
|
||||
* <p>
|
||||
* You can find more information on how profiles work with JHipster on <a href="https://www.jhipster.tech/profiles/">https://www.jhipster.tech/profiles/</a>.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initApplication() {
|
||||
Collection<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
|
||||
if (
|
||||
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) &&
|
||||
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)
|
||||
) {
|
||||
LOG.error(
|
||||
"You have misconfigured your application! It should not run " + "with both the 'dev' and 'prod' profiles at the same time."
|
||||
);
|
||||
}
|
||||
if (
|
||||
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) &&
|
||||
activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD)
|
||||
) {
|
||||
LOG.error(
|
||||
"You have misconfigured your application! It should not " + "run with both the 'dev' and 'cloud' profiles at the same time."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method, used to run the application.
|
||||
*
|
||||
* @param args the command line arguments.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
SpringApplication app = new SpringApplication(SasiedziApp.class);
|
||||
DefaultProfileUtil.addDefaultProfile(app);
|
||||
Environment env = app.run(args).getEnvironment();
|
||||
logApplicationStartup(env);
|
||||
}
|
||||
|
||||
private static void logApplicationStartup(Environment env) {
|
||||
String protocol = Optional.ofNullable(env.getProperty("server.ssl.key-store")).map(key -> "https").orElse("http");
|
||||
String applicationName = env.getProperty("spring.application.name");
|
||||
String serverPort = env.getProperty("server.port");
|
||||
String contextPath = Optional.ofNullable(env.getProperty("server.servlet.context-path"))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.orElse("/");
|
||||
String hostAddress = "localhost";
|
||||
try {
|
||||
hostAddress = InetAddress.getLocalHost().getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
LOG.warn("The host name could not be determined, using `localhost` as fallback");
|
||||
}
|
||||
LOG.info(
|
||||
CRLFLogConverter.CRLF_SAFE_MARKER,
|
||||
"""
|
||||
|
||||
----------------------------------------------------------
|
||||
\tApplication '{}' is running! Access URLs:
|
||||
\tLocal: \t\t{}://localhost:{}{}
|
||||
\tExternal: \t{}://{}:{}{}
|
||||
\tProfile(s): \t{}
|
||||
----------------------------------------------------------""",
|
||||
applicationName,
|
||||
protocol,
|
||||
serverPort,
|
||||
contextPath,
|
||||
protocol,
|
||||
hostAddress,
|
||||
serverPort,
|
||||
contextPath,
|
||||
env.getActiveProfiles().length == 0 ? env.getDefaultProfiles() : env.getActiveProfiles()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.sasiedzi.event.aop.logging;
|
||||
|
||||
import java.util.Arrays;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.Profiles;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
|
||||
/**
|
||||
* Aspect for logging execution of service and repository Spring components.
|
||||
*
|
||||
* By default, it only runs with the "dev" profile.
|
||||
*/
|
||||
@Aspect
|
||||
public class LoggingAspect {
|
||||
|
||||
private final Environment env;
|
||||
|
||||
public LoggingAspect(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointcut that matches all repositories, services and Web REST endpoints.
|
||||
*/
|
||||
@Pointcut(
|
||||
"within(@org.springframework.stereotype.Repository *)" +
|
||||
" || within(@org.springframework.stereotype.Service *)" +
|
||||
" || within(@org.springframework.web.bind.annotation.RestController *)"
|
||||
)
|
||||
public void springBeanPointcut() {
|
||||
// Method is empty as this is just a Pointcut, the implementations are in the advices.
|
||||
}
|
||||
|
||||
/**
|
||||
* Pointcut that matches all Spring beans in the application's main packages.
|
||||
*/
|
||||
@Pointcut(
|
||||
"within(com.sasiedzi.event.repository..*)" +
|
||||
" || within(com.sasiedzi.event.service..*)" +
|
||||
" || within(com.sasiedzi.event.web.rest..*)"
|
||||
)
|
||||
public void applicationPackagePointcut() {
|
||||
// Method is empty as this is just a Pointcut, the implementations are in the advices.
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the {@link Logger} associated to the given {@link JoinPoint}.
|
||||
*
|
||||
* @param joinPoint join point we want the logger for.
|
||||
* @return {@link Logger} associated to the given {@link JoinPoint}.
|
||||
*/
|
||||
private Logger logger(JoinPoint joinPoint) {
|
||||
return LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringTypeName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Advice that logs methods throwing exceptions.
|
||||
*
|
||||
* @param joinPoint join point for advice.
|
||||
* @param e exception.
|
||||
*/
|
||||
@AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", throwing = "e")
|
||||
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
|
||||
if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) {
|
||||
logger(joinPoint).error(
|
||||
"Exception in {}() with cause = '{}' and exception = '{}'",
|
||||
joinPoint.getSignature().getName(),
|
||||
e.getCause() != null ? e.getCause() : "NULL",
|
||||
e.getMessage(),
|
||||
e
|
||||
);
|
||||
} else {
|
||||
logger(joinPoint).error(
|
||||
"Exception in {}() with cause = {}",
|
||||
joinPoint.getSignature().getName(),
|
||||
e.getCause() != null ? String.valueOf(e.getCause()) : "NULL"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advice that logs when a method is entered and exited.
|
||||
*
|
||||
* @param joinPoint join point for advice.
|
||||
* @return result.
|
||||
* @throws Throwable throws {@link IllegalArgumentException}.
|
||||
*/
|
||||
@Around("applicationPackagePointcut() && springBeanPointcut()")
|
||||
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
Logger log = logger(joinPoint);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Enter: {}() with argument[s] = {}", joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
|
||||
}
|
||||
try {
|
||||
Object result = joinPoint.proceed();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Exit: {}() with result = {}", joinPoint.getSignature().getName(), result);
|
||||
}
|
||||
return result;
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("Illegal argument: {} in {}()", Arrays.toString(joinPoint.getArgs()), joinPoint.getSignature().getName());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Logging aspect.
|
||||
*/
|
||||
package com.sasiedzi.event.aop.logging;
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties specific to Sasiedzi.
|
||||
* <p>
|
||||
* Properties are configured in the {@code application.yml} file.
|
||||
* See {@link tech.jhipster.config.JHipsterProperties} for a good example.
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
|
||||
public class ApplicationProperties {
|
||||
|
||||
private final Liquibase liquibase = new Liquibase();
|
||||
|
||||
// jhipster-needle-application-properties-property
|
||||
|
||||
public Liquibase getLiquibase() {
|
||||
return liquibase;
|
||||
}
|
||||
|
||||
// jhipster-needle-application-properties-property-getter
|
||||
|
||||
public static class Liquibase {
|
||||
|
||||
private Boolean asyncStart;
|
||||
|
||||
public Boolean getAsyncStart() {
|
||||
return asyncStart;
|
||||
}
|
||||
|
||||
public void setAsyncStart(Boolean asyncStart) {
|
||||
this.asyncStart = asyncStart;
|
||||
}
|
||||
}
|
||||
// jhipster-needle-application-properties-property-class
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
|
||||
import org.springframework.boot.autoconfigure.task.TaskExecutionProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import tech.jhipster.async.ExceptionHandlingAsyncTaskExecutor;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
@Profile("!testdev & !testprod")
|
||||
public class AsyncConfiguration implements AsyncConfigurer {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AsyncConfiguration.class);
|
||||
|
||||
private final TaskExecutionProperties taskExecutionProperties;
|
||||
|
||||
public AsyncConfiguration(TaskExecutionProperties taskExecutionProperties) {
|
||||
this.taskExecutionProperties = taskExecutionProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean(name = "taskExecutor")
|
||||
public Executor getAsyncExecutor() {
|
||||
LOG.debug("Creating Async Task Executor");
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize());
|
||||
executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize());
|
||||
executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity());
|
||||
executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix());
|
||||
return new ExceptionHandlingAsyncTaskExecutor(executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new SimpleAsyncUncaughtExceptionHandler();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.pattern.CompositeConverter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
import org.springframework.boot.ansi.AnsiColor;
|
||||
import org.springframework.boot.ansi.AnsiElement;
|
||||
import org.springframework.boot.ansi.AnsiOutput;
|
||||
import org.springframework.boot.ansi.AnsiStyle;
|
||||
|
||||
/**
|
||||
* Log filter to prevent attackers from forging log entries by submitting input containing CRLF characters.
|
||||
* CRLF characters are replaced with a red colored _ character.
|
||||
*
|
||||
* @see <a href="https://owasp.org/www-community/attacks/Log_Injection">Log Forging Description</a>
|
||||
* @see <a href="https://github.com/jhipster/generator-jhipster/issues/14949">JHipster issue</a>
|
||||
*/
|
||||
public class CRLFLogConverter extends CompositeConverter<ILoggingEvent> {
|
||||
|
||||
public static final Marker CRLF_SAFE_MARKER = MarkerFactory.getMarker("CRLF_SAFE");
|
||||
|
||||
private static final String[] SAFE_LOGS = {
|
||||
"org.hibernate",
|
||||
"org.springframework.boot.autoconfigure",
|
||||
"org.springframework.boot.diagnostics",
|
||||
};
|
||||
private static final Map<String, AnsiElement> ELEMENTS;
|
||||
|
||||
static {
|
||||
Map<String, AnsiElement> ansiElements = new HashMap<>();
|
||||
ansiElements.put("faint", AnsiStyle.FAINT);
|
||||
ansiElements.put("red", AnsiColor.RED);
|
||||
ansiElements.put("green", AnsiColor.GREEN);
|
||||
ansiElements.put("yellow", AnsiColor.YELLOW);
|
||||
ansiElements.put("blue", AnsiColor.BLUE);
|
||||
ansiElements.put("magenta", AnsiColor.MAGENTA);
|
||||
ansiElements.put("cyan", AnsiColor.CYAN);
|
||||
ELEMENTS = Collections.unmodifiableMap(ansiElements);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String transform(ILoggingEvent event, String in) {
|
||||
AnsiElement element = ELEMENTS.get(getFirstOption());
|
||||
List<Marker> markers = event.getMarkerList();
|
||||
if ((markers != null && !markers.isEmpty() && markers.get(0).contains(CRLF_SAFE_MARKER)) || isLoggerSafe(event)) {
|
||||
return in;
|
||||
}
|
||||
String replacement = element == null ? "_" : toAnsiString("_", element);
|
||||
return in.replaceAll("[\n\r\t]", replacement);
|
||||
}
|
||||
|
||||
protected boolean isLoggerSafe(ILoggingEvent event) {
|
||||
for (String safeLogger : SAFE_LOGS) {
|
||||
if (event.getLoggerName().startsWith(safeLogger)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected String toAnsiString(String in, AnsiElement element) {
|
||||
return AnsiOutput.toString(element, in);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
/**
|
||||
* Application constants.
|
||||
*/
|
||||
public final class Constants {
|
||||
|
||||
// Regex for acceptable logins
|
||||
public static final String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$";
|
||||
|
||||
public static final String SYSTEM = "system";
|
||||
public static final String DEFAULT_LANGUAGE = "en";
|
||||
|
||||
private Constants() {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories({ "com.sasiedzi.event.repository" })
|
||||
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
|
||||
@EnableTransactionManagement
|
||||
public class DatabaseConfiguration {}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Configure the converters to use the ISO format for dates by default.
|
||||
*/
|
||||
@Configuration
|
||||
public class DateTimeFormatConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
|
||||
registrar.setUseIsoFormat(true);
|
||||
registrar.registerFormatters(registry);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import com.fasterxml.jackson.datatype.hibernate6.Hibernate6Module;
|
||||
import com.fasterxml.jackson.datatype.hibernate6.Hibernate6Module.Feature;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class JacksonConfiguration {
|
||||
|
||||
/**
|
||||
* Support for Java date and time API.
|
||||
* @return the corresponding Jackson module.
|
||||
*/
|
||||
@Bean
|
||||
public JavaTimeModule javaTimeModule() {
|
||||
return new JavaTimeModule();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Jdk8Module jdk8TimeModule() {
|
||||
return new Jdk8Module();
|
||||
}
|
||||
|
||||
/*
|
||||
* Support for Hibernate types in Jackson.
|
||||
*/
|
||||
@Bean
|
||||
public Hibernate6Module hibernate6Module() {
|
||||
return new Hibernate6Module().configure(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.sql.DataSource;
|
||||
import liquibase.integration.spring.SpringLiquibase;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseDataSource;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
import tech.jhipster.config.liquibase.SpringLiquibaseUtil;
|
||||
|
||||
@Configuration
|
||||
public class LiquibaseConfiguration {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LiquibaseConfiguration.class);
|
||||
|
||||
private final Environment env;
|
||||
|
||||
public LiquibaseConfiguration(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
@Value("${application.liquibase.async-start:true}")
|
||||
private Boolean asyncStart;
|
||||
|
||||
@Bean
|
||||
public SpringLiquibase liquibase(
|
||||
@Qualifier("taskExecutor") Executor executor,
|
||||
LiquibaseProperties liquibaseProperties,
|
||||
@LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource,
|
||||
ObjectProvider<DataSource> dataSource,
|
||||
DataSourceProperties dataSourceProperties
|
||||
) {
|
||||
SpringLiquibase liquibase;
|
||||
if (Boolean.TRUE.equals(asyncStart)) {
|
||||
liquibase = SpringLiquibaseUtil.createAsyncSpringLiquibase(
|
||||
this.env,
|
||||
executor,
|
||||
liquibaseDataSource.getIfAvailable(),
|
||||
liquibaseProperties,
|
||||
dataSource.getIfUnique(),
|
||||
dataSourceProperties
|
||||
);
|
||||
} else {
|
||||
liquibase = SpringLiquibaseUtil.createSpringLiquibase(
|
||||
liquibaseDataSource.getIfAvailable(),
|
||||
liquibaseProperties,
|
||||
dataSource.getIfUnique(),
|
||||
dataSourceProperties
|
||||
);
|
||||
}
|
||||
liquibase.setChangeLog("classpath:config/liquibase/master.xml");
|
||||
liquibase.setContexts(liquibaseProperties.getContexts());
|
||||
liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema());
|
||||
liquibase.setLiquibaseSchema(liquibaseProperties.getLiquibaseSchema());
|
||||
liquibase.setLiquibaseTablespace(liquibaseProperties.getLiquibaseTablespace());
|
||||
liquibase.setDatabaseChangeLogLockTable(liquibaseProperties.getDatabaseChangeLogLockTable());
|
||||
liquibase.setDatabaseChangeLogTable(liquibaseProperties.getDatabaseChangeLogTable());
|
||||
liquibase.setDropFirst(liquibaseProperties.isDropFirst());
|
||||
liquibase.setLabelFilter(liquibaseProperties.getLabelFilter());
|
||||
liquibase.setChangeLogParameters(liquibaseProperties.getParameters());
|
||||
liquibase.setRollbackFile(liquibaseProperties.getRollbackFile());
|
||||
liquibase.setTestRollbackOnUpdate(liquibaseProperties.isTestRollbackOnUpdate());
|
||||
if (env.matchesProfiles(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE)) {
|
||||
liquibase.setShouldRun(false);
|
||||
} else {
|
||||
liquibase.setShouldRun(liquibaseProperties.isEnabled());
|
||||
LOG.debug("Configuring Liquibase");
|
||||
}
|
||||
return liquibase;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import com.sasiedzi.event.aop.logging.LoggingAspect;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.core.env.Environment;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy
|
||||
public class LoggingAspectConfiguration {
|
||||
|
||||
@Bean
|
||||
@Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
|
||||
public LoggingAspect loggingAspect(Environment env) {
|
||||
return new LoggingAspect(env);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import static tech.jhipster.config.logging.LoggingUtils.*;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
|
||||
/*
|
||||
* Configures the console and Logstash log appenders from the app properties
|
||||
*/
|
||||
@Configuration
|
||||
public class LoggingConfiguration {
|
||||
|
||||
public LoggingConfiguration(
|
||||
@Value("${spring.application.name}") String appName,
|
||||
@Value("${server.port}") String serverPort,
|
||||
JHipsterProperties jHipsterProperties,
|
||||
ObjectMapper mapper
|
||||
) throws JsonProcessingException {
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("app_name", appName);
|
||||
map.put("app_port", serverPort);
|
||||
String customFields = mapper.writeValueAsString(map);
|
||||
|
||||
JHipsterProperties.Logging loggingProperties = jHipsterProperties.getLogging();
|
||||
JHipsterProperties.Logging.Logstash logstashProperties = loggingProperties.getLogstash();
|
||||
|
||||
if (loggingProperties.isUseJsonFormat()) {
|
||||
addJsonConsoleAppender(context, customFields);
|
||||
}
|
||||
if (logstashProperties.isEnabled()) {
|
||||
addLogstashTcpSocketAppender(context, customFields, logstashProperties);
|
||||
}
|
||||
if (loggingProperties.isUseJsonFormat() || logstashProperties.isEnabled()) {
|
||||
addContextListener(context, customFields, loggingProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import java.time.Duration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
|
||||
@Configuration
|
||||
public class OAuth2Configuration {
|
||||
|
||||
@Bean
|
||||
public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
ClientRegistrationRepository clientRegistrationRepository,
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository
|
||||
) {
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository,
|
||||
authorizedClientRepository
|
||||
);
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.authorizationCode()
|
||||
.refreshToken(builder -> builder.clockSkew(Duration.ofMinutes(1)))
|
||||
.clientCredentials()
|
||||
.build()
|
||||
);
|
||||
|
||||
return authorizedClientManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
import static org.springframework.security.oauth2.core.oidc.StandardClaimNames.PREFERRED_USERNAME;
|
||||
|
||||
import com.sasiedzi.event.security.*;
|
||||
import com.sasiedzi.event.security.SecurityUtils;
|
||||
import com.sasiedzi.event.security.oauth2.AudienceValidator;
|
||||
import com.sasiedzi.event.security.oauth2.CustomClaimConverter;
|
||||
import com.sasiedzi.event.web.filter.SpaWebFilter;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||
import org.springframework.security.oauth2.jwt.*;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
import org.springframework.security.web.csrf.*;
|
||||
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
import tech.jhipster.web.filter.CookieCsrfFilter;
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
public class SecurityConfiguration {
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
|
||||
@Value("${spring.security.oauth2.client.provider.oidc.issuer-uri}")
|
||||
private String issuerUri;
|
||||
|
||||
public SecurityConfiguration(JHipsterProperties jHipsterProperties) {
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception {
|
||||
http
|
||||
.cors(withDefaults())
|
||||
.csrf(csrf ->
|
||||
csrf
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||
.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler())
|
||||
)
|
||||
.addFilterAfter(new SpaWebFilter(), BasicAuthenticationFilter.class)
|
||||
.addFilterAfter(new CookieCsrfFilter(), BasicAuthenticationFilter.class)
|
||||
.headers(headers ->
|
||||
headers
|
||||
.contentSecurityPolicy(csp -> csp.policyDirectives(jHipsterProperties.getSecurity().getContentSecurityPolicy()))
|
||||
.frameOptions(FrameOptionsConfig::sameOrigin)
|
||||
.referrerPolicy(referrer -> referrer.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
|
||||
.permissionsPolicy(permissions ->
|
||||
permissions.policy(
|
||||
"camera=(), fullscreen=(self), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=()"
|
||||
)
|
||||
)
|
||||
)
|
||||
.authorizeHttpRequests(authz ->
|
||||
// prettier-ignore
|
||||
authz
|
||||
.requestMatchers(mvc.pattern("/index.html"), mvc.pattern("/*.js"), mvc.pattern("/*.txt"), mvc.pattern("/*.json"), mvc.pattern("/*.map"), mvc.pattern("/*.css")).permitAll()
|
||||
.requestMatchers(mvc.pattern("/*.ico"), mvc.pattern("/*.png"), mvc.pattern("/*.svg"), mvc.pattern("/*.webapp")).permitAll()
|
||||
.requestMatchers(mvc.pattern("/assets/**")).permitAll()
|
||||
.requestMatchers(mvc.pattern("/swagger-ui/**")).permitAll()
|
||||
.requestMatchers(mvc.pattern("/api/authenticate")).permitAll()
|
||||
.requestMatchers(mvc.pattern("/api/auth-info")).permitAll()
|
||||
.requestMatchers(mvc.pattern("/api/admin/**")).hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.requestMatchers(mvc.pattern("/api/**")).authenticated()
|
||||
.requestMatchers(mvc.pattern("/v3/api-docs/**")).hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
.requestMatchers(mvc.pattern("/management/health")).permitAll()
|
||||
.requestMatchers(mvc.pattern("/management/health/**")).permitAll()
|
||||
.requestMatchers(mvc.pattern("/management/info")).permitAll()
|
||||
.requestMatchers(mvc.pattern("/management/prometheus")).permitAll()
|
||||
.requestMatchers(mvc.pattern("/management/**")).hasAuthority(AuthoritiesConstants.ADMIN)
|
||||
)
|
||||
.oauth2Login(oauth2 -> oauth2.loginPage("/").userInfoEndpoint(userInfo -> userInfo.oidcUserService(this.oidcUserService())))
|
||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(authenticationConverter())))
|
||||
.oauth2Client(withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
|
||||
return new MvcRequestMatcher.Builder(introspector);
|
||||
}
|
||||
|
||||
Converter<Jwt, AbstractAuthenticationToken> authenticationConverter() {
|
||||
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
||||
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
|
||||
new Converter<Jwt, Collection<GrantedAuthority>>() {
|
||||
@Override
|
||||
public Collection<GrantedAuthority> convert(Jwt jwt) {
|
||||
return SecurityUtils.extractAuthorityFromClaims(jwt.getClaims());
|
||||
}
|
||||
}
|
||||
);
|
||||
jwtAuthenticationConverter.setPrincipalClaimName(PREFERRED_USERNAME);
|
||||
return jwtAuthenticationConverter;
|
||||
}
|
||||
|
||||
OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
|
||||
final OidcUserService delegate = new OidcUserService();
|
||||
|
||||
return userRequest -> {
|
||||
OidcUser oidcUser = delegate.loadUser(userRequest);
|
||||
return new DefaultOidcUser(oidcUser.getAuthorities(), oidcUser.getIdToken(), oidcUser.getUserInfo(), PREFERRED_USERNAME);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map authorities from "groups" or "roles" claim in ID Token.
|
||||
*
|
||||
* @return a {@link GrantedAuthoritiesMapper} that maps groups from
|
||||
* the IdP to Spring Security Authorities.
|
||||
*/
|
||||
@Bean
|
||||
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||
return authorities -> {
|
||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||
|
||||
authorities.forEach(authority -> {
|
||||
// Check for OidcUserAuthority because Spring Security 5.2 returns
|
||||
// each scope as a GrantedAuthority, which we don't care about.
|
||||
if (authority instanceof OidcUserAuthority) {
|
||||
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority;
|
||||
mappedAuthorities.addAll(SecurityUtils.extractAuthorityFromClaims(oidcUserAuthority.getUserInfo().getClaims()));
|
||||
}
|
||||
});
|
||||
return mappedAuthorities;
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder(ClientRegistrationRepository clientRegistrationRepository, RestTemplateBuilder restTemplateBuilder) {
|
||||
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuerUri);
|
||||
|
||||
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(jHipsterProperties.getSecurity().getOauth2().getAudience());
|
||||
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri);
|
||||
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
|
||||
|
||||
jwtDecoder.setJwtValidator(withAudience);
|
||||
jwtDecoder.setClaimSetConverter(
|
||||
new CustomClaimConverter(clientRegistrationRepository.findByRegistrationId("oidc"), restTemplateBuilder.build())
|
||||
);
|
||||
|
||||
return jwtDecoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom CSRF handler to provide BREACH protection.
|
||||
*
|
||||
* @see <a href="https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript-spa">Spring Security Documentation - Integrating with CSRF Protection</a>
|
||||
* @see <a href="https://github.com/jhipster/generator-jhipster/pull/25907">JHipster - use customized SpaCsrfTokenRequestHandler to handle CSRF token</a>
|
||||
* @see <a href="https://stackoverflow.com/q/74447118/65681">CSRF protection not working with Spring Security 6</a>
|
||||
*/
|
||||
static final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
|
||||
|
||||
private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
|
||||
/*
|
||||
* Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
|
||||
* the CsrfToken when it is rendered in the response body.
|
||||
*/
|
||||
this.delegate.handle(request, response, csrfToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
|
||||
/*
|
||||
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
|
||||
* to resolve the CsrfToken. This applies when a single-page application includes
|
||||
* the header value automatically, which was obtained via a cookie containing the
|
||||
* raw CsrfToken.
|
||||
*/
|
||||
if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
|
||||
return super.resolveCsrfTokenValue(request, csrfToken);
|
||||
}
|
||||
/*
|
||||
* In all other cases (e.g. if the request contains a request parameter), use
|
||||
* XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
|
||||
* when a server-side rendered form includes the _csrf request parameter as a
|
||||
* hidden input.
|
||||
*/
|
||||
return this.delegate.resolveCsrfTokenValue(request, csrfToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
|
||||
@Configuration
|
||||
@Profile({ JHipsterConstants.SPRING_PROFILE_PRODUCTION })
|
||||
public class StaticResourcesWebConfiguration implements WebMvcConfigurer {
|
||||
|
||||
protected static final String[] RESOURCE_LOCATIONS = { "classpath:/static/", "classpath:/static/content/", "classpath:/static/i18n/" };
|
||||
protected static final String[] RESOURCE_PATHS = { "/*.js", "/*.css", "/*.svg", "/*.png", "*.ico", "/content/**", "/i18n/*" };
|
||||
|
||||
private final JHipsterProperties jhipsterProperties;
|
||||
|
||||
public StaticResourcesWebConfiguration(JHipsterProperties jHipsterProperties) {
|
||||
this.jhipsterProperties = jHipsterProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
ResourceHandlerRegistration resourceHandlerRegistration = appendResourceHandler(registry);
|
||||
initializeResourceHandler(resourceHandlerRegistration);
|
||||
}
|
||||
|
||||
protected ResourceHandlerRegistration appendResourceHandler(ResourceHandlerRegistry registry) {
|
||||
return registry.addResourceHandler(RESOURCE_PATHS);
|
||||
}
|
||||
|
||||
protected void initializeResourceHandler(ResourceHandlerRegistration resourceHandlerRegistration) {
|
||||
resourceHandlerRegistration.addResourceLocations(RESOURCE_LOCATIONS).setCacheControl(getCacheControl());
|
||||
}
|
||||
|
||||
protected CacheControl getCacheControl() {
|
||||
return CacheControl.maxAge(getJHipsterHttpCacheProperty(), TimeUnit.DAYS).cachePublic();
|
||||
}
|
||||
|
||||
private int getJHipsterHttpCacheProperty() {
|
||||
return jhipsterProperties.getHttp().getCache().getTimeToLiveInDays();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.sasiedzi.event.config;
|
||||
|
||||
import static java.net.URLDecoder.decode;
|
||||
|
||||
import jakarta.servlet.*;
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.web.server.*;
|
||||
import org.springframework.boot.web.servlet.ServletContextInitializer;
|
||||
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
|
||||
/**
|
||||
* Configuration of web application with Servlet 3.0 APIs.
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfigurer implements ServletContextInitializer, WebServerFactoryCustomizer<WebServerFactory> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WebConfigurer.class);
|
||||
|
||||
private final Environment env;
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
|
||||
public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) {
|
||||
this.env = env;
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartup(ServletContext servletContext) {
|
||||
if (env.getActiveProfiles().length != 0) {
|
||||
LOG.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles());
|
||||
}
|
||||
|
||||
LOG.info("Web application fully configured");
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the Servlet engine: Mime types, the document root, the cache.
|
||||
*/
|
||||
@Override
|
||||
public void customize(WebServerFactory server) {
|
||||
// When running in an IDE or with ./mvnw spring-boot:run, set location of the static web assets.
|
||||
setLocationForStaticAssets(server);
|
||||
}
|
||||
|
||||
private void setLocationForStaticAssets(WebServerFactory server) {
|
||||
if (server instanceof ConfigurableServletWebServerFactory servletWebServer) {
|
||||
File root;
|
||||
String prefixPath = resolvePathPrefix();
|
||||
root = new File(prefixPath + "target/classes/static/");
|
||||
if (root.exists() && root.isDirectory()) {
|
||||
servletWebServer.setDocumentRoot(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve path prefix to static resources.
|
||||
*/
|
||||
private String resolvePathPrefix() {
|
||||
String fullExecutablePath = decode(this.getClass().getResource("").getPath(), StandardCharsets.UTF_8);
|
||||
String rootPath = Paths.get(".").toUri().normalize().getPath();
|
||||
String extractedPath = fullExecutablePath.replace(rootPath, "");
|
||||
int extractionEndIndex = extractedPath.indexOf("target/");
|
||||
if (extractionEndIndex <= 0) {
|
||||
return "";
|
||||
}
|
||||
return extractedPath.substring(0, extractionEndIndex);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = jHipsterProperties.getCors();
|
||||
if (!CollectionUtils.isEmpty(config.getAllowedOrigins()) || !CollectionUtils.isEmpty(config.getAllowedOriginPatterns())) {
|
||||
LOG.debug("Registering CORS filter");
|
||||
source.registerCorsConfiguration("/api/**", config);
|
||||
source.registerCorsConfiguration("/management/**", config);
|
||||
source.registerCorsConfiguration("/v3/api-docs", config);
|
||||
source.registerCorsConfiguration("/swagger-ui/**", config);
|
||||
}
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Application configuration.
|
||||
*/
|
||||
package com.sasiedzi.event.config;
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.sasiedzi.event.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
/**
|
||||
* Base abstract class for entities which will hold definitions for created, last modified, created by,
|
||||
* last modified by attributes.
|
||||
*/
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
@JsonIgnoreProperties(value = { "createdBy", "createdDate", "lastModifiedBy", "lastModifiedDate" }, allowGetters = true)
|
||||
public abstract class AbstractAuditingEntity<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public abstract T getId();
|
||||
|
||||
@CreatedBy
|
||||
@Column(name = "created_by", nullable = false, length = 50, updatable = false)
|
||||
private String createdBy;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_date", updatable = false)
|
||||
private Instant createdDate = Instant.now();
|
||||
|
||||
@LastModifiedBy
|
||||
@Column(name = "last_modified_by", length = 50)
|
||||
private String lastModifiedBy;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(name = "last_modified_date")
|
||||
private Instant lastModifiedDate = Instant.now();
|
||||
|
||||
public String getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public void setCreatedBy(String createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
|
||||
public Instant getCreatedDate() {
|
||||
return createdDate;
|
||||
}
|
||||
|
||||
public void setCreatedDate(Instant createdDate) {
|
||||
this.createdDate = createdDate;
|
||||
}
|
||||
|
||||
public String getLastModifiedBy() {
|
||||
return lastModifiedBy;
|
||||
}
|
||||
|
||||
public void setLastModifiedBy(String lastModifiedBy) {
|
||||
this.lastModifiedBy = lastModifiedBy;
|
||||
}
|
||||
|
||||
public Instant getLastModifiedDate() {
|
||||
return lastModifiedDate;
|
||||
}
|
||||
|
||||
public void setLastModifiedDate(Instant lastModifiedDate) {
|
||||
this.lastModifiedDate = lastModifiedDate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.sasiedzi.event.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import org.springframework.data.domain.Persistable;
|
||||
|
||||
/**
|
||||
* A Authority.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "jhi_authority")
|
||||
@JsonIgnoreProperties(value = { "new", "id" })
|
||||
@SuppressWarnings("common-java:DuplicatedBlocks")
|
||||
public class Authority implements Serializable, Persistable<String> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotNull
|
||||
@Size(max = 50)
|
||||
@Id
|
||||
@Column(name = "name", length = 50, nullable = false)
|
||||
private String name;
|
||||
|
||||
@org.springframework.data.annotation.Transient
|
||||
@Transient
|
||||
private boolean isPersisted;
|
||||
|
||||
// jhipster-needle-entity-add-field - JHipster will add fields here
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Authority name(String name) {
|
||||
this.setName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
@PostPersist
|
||||
public void updateEntityState() {
|
||||
this.setIsPersisted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@org.springframework.data.annotation.Transient
|
||||
@Transient
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return !this.isPersisted;
|
||||
}
|
||||
|
||||
public Authority setIsPersisted() {
|
||||
this.isPersisted = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Authority)) {
|
||||
return false;
|
||||
}
|
||||
return getName() != null && getName().equals(((Authority) o).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(getName());
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Authority{" +
|
||||
"name=" + getName() +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.sasiedzi.event.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.sasiedzi.event.config.Constants;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hibernate.annotations.BatchSize;
|
||||
|
||||
/**
|
||||
* A user.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "jhi_user")
|
||||
public class User extends AbstractAuditingEntity<String> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@NotNull
|
||||
@Pattern(regexp = Constants.LOGIN_REGEX)
|
||||
@Size(min = 1, max = 50)
|
||||
@Column(length = 50, unique = true, nullable = false)
|
||||
private String login;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "first_name", length = 50)
|
||||
private String firstName;
|
||||
|
||||
@Size(max = 50)
|
||||
@Column(name = "last_name", length = 50)
|
||||
private String lastName;
|
||||
|
||||
@Email
|
||||
@Size(min = 5, max = 254)
|
||||
@Column(length = 254, unique = true)
|
||||
private String email;
|
||||
|
||||
@NotNull
|
||||
@Column(nullable = false)
|
||||
private boolean activated = false;
|
||||
|
||||
@Size(min = 2, max = 10)
|
||||
@Column(name = "lang_key", length = 10)
|
||||
private String langKey;
|
||||
|
||||
@Size(max = 256)
|
||||
@Column(name = "image_url", length = 256)
|
||||
private String imageUrl;
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "jhi_user_authority",
|
||||
joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") },
|
||||
inverseJoinColumns = { @JoinColumn(name = "authority_name", referencedColumnName = "name") }
|
||||
)
|
||||
@BatchSize(size = 20)
|
||||
private Set<Authority> authorities = new HashSet<>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
// Lowercase the login before saving it in database
|
||||
public void setLogin(String login) {
|
||||
this.login = StringUtils.lowerCase(login, Locale.ENGLISH);
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getImageUrl() {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
public void setImageUrl(String imageUrl) {
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
|
||||
public boolean isActivated() {
|
||||
return activated;
|
||||
}
|
||||
|
||||
public void setActivated(boolean activated) {
|
||||
this.activated = activated;
|
||||
}
|
||||
|
||||
public String getLangKey() {
|
||||
return langKey;
|
||||
}
|
||||
|
||||
public void setLangKey(String langKey) {
|
||||
this.langKey = langKey;
|
||||
}
|
||||
|
||||
public Set<Authority> getAuthorities() {
|
||||
return authorities;
|
||||
}
|
||||
|
||||
public void setAuthorities(Set<Authority> authorities) {
|
||||
this.authorities = authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof User)) {
|
||||
return false;
|
||||
}
|
||||
return id != null && id.equals(((User) o).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
|
||||
return getClass().hashCode();
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"login='" + login + '\'' +
|
||||
", firstName='" + firstName + '\'' +
|
||||
", lastName='" + lastName + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", imageUrl='" + imageUrl + '\'' +
|
||||
", activated='" + activated + '\'' +
|
||||
", langKey='" + langKey + '\'' +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Domain objects.
|
||||
*/
|
||||
package com.sasiedzi.event.domain;
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Application root.
|
||||
*/
|
||||
package com.sasiedzi.event;
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.sasiedzi.event.repository;
|
||||
|
||||
import com.sasiedzi.event.domain.Authority;
|
||||
import org.springframework.data.jpa.repository.*;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for the Authority entity.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Repository
|
||||
public interface AuthorityRepository extends JpaRepository<Authority, String> {}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.sasiedzi.event.repository;
|
||||
|
||||
import com.sasiedzi.event.domain.User;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.domain.*;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for the {@link User} entity.
|
||||
*/
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, String> {
|
||||
Optional<User> findOneByLogin(String login);
|
||||
|
||||
@EntityGraph(attributePaths = "authorities")
|
||||
Optional<User> findOneWithAuthoritiesByLogin(String login);
|
||||
|
||||
Page<User> findAllByIdNotNullAndActivatedIsTrue(Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Repository layer.
|
||||
*/
|
||||
package com.sasiedzi.event.repository;
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.sasiedzi.event.security;
|
||||
|
||||
/**
|
||||
* Constants for Spring Security authorities.
|
||||
*/
|
||||
public final class AuthoritiesConstants {
|
||||
|
||||
public static final String ADMIN = "ROLE_ADMIN";
|
||||
|
||||
public static final String USER = "ROLE_USER";
|
||||
|
||||
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
|
||||
|
||||
private AuthoritiesConstants() {}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.sasiedzi.event.security;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
|
||||
/**
|
||||
* Utility class for Spring Security.
|
||||
*/
|
||||
public final class SecurityUtils {
|
||||
|
||||
public static final String CLAIMS_NAMESPACE = "https://www.jhipster.tech/";
|
||||
|
||||
private SecurityUtils() {}
|
||||
|
||||
/**
|
||||
* Get the login of the current user.
|
||||
*
|
||||
* @return the login of the current user.
|
||||
*/
|
||||
public static Optional<String> getCurrentUserLogin() {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication()));
|
||||
}
|
||||
|
||||
private static String extractPrincipal(Authentication authentication) {
|
||||
if (authentication == null) {
|
||||
return null;
|
||||
} else if (authentication.getPrincipal() instanceof UserDetails springSecurityUser) {
|
||||
return springSecurityUser.getUsername();
|
||||
} else if (authentication instanceof JwtAuthenticationToken) {
|
||||
return (String) ((JwtAuthenticationToken) authentication).getToken().getClaims().get("preferred_username");
|
||||
} else if (authentication.getPrincipal() instanceof DefaultOidcUser) {
|
||||
Map<String, Object> attributes = ((DefaultOidcUser) authentication.getPrincipal()).getAttributes();
|
||||
if (attributes.containsKey("preferred_username")) {
|
||||
return (String) attributes.get("preferred_username");
|
||||
}
|
||||
} else if (authentication.getPrincipal() instanceof String s) {
|
||||
return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is authenticated.
|
||||
*
|
||||
* @return true if the user is authenticated, false otherwise.
|
||||
*/
|
||||
public static boolean isAuthenticated() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
return authentication != null && getAuthorities(authentication).noneMatch(AuthoritiesConstants.ANONYMOUS::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current user has any of the authorities.
|
||||
*
|
||||
* @param authorities the authorities to check.
|
||||
* @return true if the current user has any of the authorities, false otherwise.
|
||||
*/
|
||||
public static boolean hasCurrentUserAnyOfAuthorities(String... authorities) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
return (
|
||||
authentication != null && getAuthorities(authentication).anyMatch(authority -> Arrays.asList(authorities).contains(authority))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current user has none of the authorities.
|
||||
*
|
||||
* @param authorities the authorities to check.
|
||||
* @return true if the current user has none of the authorities, false otherwise.
|
||||
*/
|
||||
public static boolean hasCurrentUserNoneOfAuthorities(String... authorities) {
|
||||
return !hasCurrentUserAnyOfAuthorities(authorities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current user has a specific authority.
|
||||
*
|
||||
* @param authority the authority to check.
|
||||
* @return true if the current user has the authority, false otherwise.
|
||||
*/
|
||||
public static boolean hasCurrentUserThisAuthority(String authority) {
|
||||
return hasCurrentUserAnyOfAuthorities(authority);
|
||||
}
|
||||
|
||||
private static Stream<String> getAuthorities(Authentication authentication) {
|
||||
Collection<? extends GrantedAuthority> authorities = authentication instanceof JwtAuthenticationToken
|
||||
? extractAuthorityFromClaims(((JwtAuthenticationToken) authentication).getToken().getClaims())
|
||||
: authentication.getAuthorities();
|
||||
return authorities.stream().map(GrantedAuthority::getAuthority);
|
||||
}
|
||||
|
||||
public static List<GrantedAuthority> extractAuthorityFromClaims(Map<String, Object> claims) {
|
||||
return mapRolesToGrantedAuthorities(getRolesFromClaims(claims));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Collection<String> getRolesFromClaims(Map<String, Object> claims) {
|
||||
return (Collection<String>) claims.getOrDefault(
|
||||
"groups",
|
||||
claims.getOrDefault("roles", claims.getOrDefault(CLAIMS_NAMESPACE + "roles", new ArrayList<>()))
|
||||
);
|
||||
}
|
||||
|
||||
private static List<GrantedAuthority> mapRolesToGrantedAuthorities(Collection<String> roles) {
|
||||
return roles.stream().filter(role -> role.startsWith("ROLE_")).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.sasiedzi.event.security;
|
||||
|
||||
import com.sasiedzi.event.config.Constants;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Implementation of {@link AuditorAware} based on Spring Security.
|
||||
*/
|
||||
@Component
|
||||
public class SpringSecurityAuditorAware implements AuditorAware<String> {
|
||||
|
||||
@Override
|
||||
public Optional<String> getCurrentAuditor() {
|
||||
return Optional.of(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.sasiedzi.event.security.oauth2;
|
||||
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AudienceValidator.class);
|
||||
private final OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);
|
||||
|
||||
private final List<String> allowedAudience;
|
||||
|
||||
public AudienceValidator(List<String> allowedAudience) {
|
||||
Assert.notEmpty(allowedAudience, "Allowed audience should not be null or empty.");
|
||||
this.allowedAudience = allowedAudience;
|
||||
}
|
||||
|
||||
public OAuth2TokenValidatorResult validate(Jwt jwt) {
|
||||
List<String> audience = jwt.getAudience();
|
||||
if (audience.stream().anyMatch(allowedAudience::contains)) {
|
||||
return OAuth2TokenValidatorResult.success();
|
||||
} else {
|
||||
LOG.warn("Invalid audience: {}", audience);
|
||||
return OAuth2TokenValidatorResult.failure(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.sasiedzi.event.security.oauth2;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.sasiedzi.event.security.SecurityUtils;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.StreamSupport;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.jwt.MappedJwtClaimSetConverter;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
|
||||
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* Claim converter to add custom claims by retrieving the user from the userinfo endpoint.
|
||||
*/
|
||||
public class CustomClaimConverter implements Converter<Map<String, Object>, Map<String, Object>> {
|
||||
|
||||
private final BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
|
||||
|
||||
private final MappedJwtClaimSetConverter delegate = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
private final ClientRegistration registration;
|
||||
|
||||
// See https://github.com/jhipster/generator-jhipster/issues/18868
|
||||
// We don't use a distributed cache or the user selected cache implementation here on purpose
|
||||
private final Cache<String, ObjectNode> users = Caffeine.newBuilder()
|
||||
.maximumSize(10_000)
|
||||
.expireAfterWrite(Duration.ofHours(1))
|
||||
.recordStats()
|
||||
.build();
|
||||
|
||||
public CustomClaimConverter(ClientRegistration registration, RestTemplate restTemplate) {
|
||||
this.registration = registration;
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
public Map<String, Object> convert(Map<String, Object> claims) {
|
||||
Map<String, Object> convertedClaims = this.delegate.convert(claims);
|
||||
// Only look up user information if identity claims are missing
|
||||
if (claims.containsKey("given_name") && claims.containsKey("family_name")) {
|
||||
return convertedClaims;
|
||||
}
|
||||
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
|
||||
if (attributes instanceof ServletRequestAttributes) {
|
||||
// Retrieve and set the token
|
||||
String token = bearerTokenResolver.resolve(((ServletRequestAttributes) attributes).getRequest());
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(token);
|
||||
|
||||
// Retrieve user info from OAuth provider if not already loaded
|
||||
ObjectNode user = users.get(claims.get("sub").toString(), s -> {
|
||||
ResponseEntity<ObjectNode> userInfo = restTemplate.exchange(
|
||||
registration.getProviderDetails().getUserInfoEndpoint().getUri(),
|
||||
HttpMethod.GET,
|
||||
new HttpEntity<String>(headers),
|
||||
ObjectNode.class
|
||||
);
|
||||
return userInfo.getBody();
|
||||
});
|
||||
|
||||
// Add custom claims
|
||||
if (user != null) {
|
||||
convertedClaims.put("preferred_username", user.get("preferred_username").asText());
|
||||
if (user.has("given_name")) {
|
||||
convertedClaims.put("given_name", user.get("given_name").asText());
|
||||
}
|
||||
if (user.has("family_name")) {
|
||||
convertedClaims.put("family_name", user.get("family_name").asText());
|
||||
}
|
||||
if (user.has("email")) {
|
||||
convertedClaims.put("email", user.get("email").asText());
|
||||
}
|
||||
// Allow full name in a name claim - happens with Auth0
|
||||
if (user.has("name")) {
|
||||
String[] name = user.get("name").asText().split("\\s+");
|
||||
if (name.length > 0) {
|
||||
convertedClaims.put("given_name", name[0]);
|
||||
convertedClaims.put("family_name", String.join(" ", Arrays.copyOfRange(name, 1, name.length)));
|
||||
}
|
||||
}
|
||||
if (user.has("groups")) {
|
||||
List<String> groups = StreamSupport.stream(user.get("groups").spliterator(), false).map(JsonNode::asText).toList();
|
||||
convertedClaims.put("groups", groups);
|
||||
}
|
||||
if (user.has(SecurityUtils.CLAIMS_NAMESPACE + "roles")) {
|
||||
List<String> roles = StreamSupport.stream(user.get(SecurityUtils.CLAIMS_NAMESPACE + "roles").spliterator(), false)
|
||||
.map(JsonNode::asText)
|
||||
.toList();
|
||||
convertedClaims.put("roles", roles);
|
||||
}
|
||||
}
|
||||
}
|
||||
return convertedClaims;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* This package file was generated by JHipster
|
||||
*/
|
||||
package com.sasiedzi.event.security.oauth2;
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Application security utilities.
|
||||
*/
|
||||
package com.sasiedzi.event.security;
|
||||
@@ -0,0 +1,226 @@
|
||||
package com.sasiedzi.event.service;
|
||||
|
||||
import com.sasiedzi.event.config.Constants;
|
||||
import com.sasiedzi.event.domain.Authority;
|
||||
import com.sasiedzi.event.domain.User;
|
||||
import com.sasiedzi.event.repository.AuthorityRepository;
|
||||
import com.sasiedzi.event.repository.UserRepository;
|
||||
import com.sasiedzi.event.security.SecurityUtils;
|
||||
import com.sasiedzi.event.service.dto.AdminUserDTO;
|
||||
import com.sasiedzi.event.service.dto.UserDTO;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Service class for managing users.
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
public class UserService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UserService.class);
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final AuthorityRepository authorityRepository;
|
||||
|
||||
public UserService(UserRepository userRepository, AuthorityRepository authorityRepository) {
|
||||
this.userRepository = userRepository;
|
||||
this.authorityRepository = authorityRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update basic information (first name, last name, email, language) for the current user.
|
||||
*
|
||||
* @param firstName first name of user.
|
||||
* @param lastName last name of user.
|
||||
* @param email email id of user.
|
||||
* @param langKey language key.
|
||||
* @param imageUrl image URL of user.
|
||||
*/
|
||||
public void updateUser(String firstName, String lastName, String email, String langKey, String imageUrl) {
|
||||
SecurityUtils.getCurrentUserLogin()
|
||||
.flatMap(userRepository::findOneByLogin)
|
||||
.ifPresent(user -> {
|
||||
user.setFirstName(firstName);
|
||||
user.setLastName(lastName);
|
||||
if (email != null) {
|
||||
user.setEmail(email.toLowerCase());
|
||||
}
|
||||
user.setLangKey(langKey);
|
||||
user.setImageUrl(imageUrl);
|
||||
userRepository.save(user);
|
||||
LOG.debug("Changed Information for User: {}", user);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Page<AdminUserDTO> getAllManagedUsers(Pageable pageable) {
|
||||
return userRepository.findAll(pageable).map(AdminUserDTO::new);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Page<UserDTO> getAllPublicUsers(Pageable pageable) {
|
||||
return userRepository.findAllByIdNotNullAndActivatedIsTrue(pageable).map(UserDTO::new);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<User> getUserWithAuthoritiesByLogin(String login) {
|
||||
return userRepository.findOneWithAuthoritiesByLogin(login);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all the authorities.
|
||||
* @return a list of all the authorities.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public List<String> getAuthorities() {
|
||||
return authorityRepository.findAll().stream().map(Authority::getName).toList();
|
||||
}
|
||||
|
||||
private User syncUserWithIdP(Map<String, Object> details, User user) {
|
||||
// save authorities in to sync user roles/groups between IdP and JHipster's local database
|
||||
Collection<String> dbAuthorities = getAuthorities();
|
||||
Collection<String> userAuthorities = user.getAuthorities().stream().map(Authority::getName).toList();
|
||||
for (String authority : userAuthorities) {
|
||||
if (!dbAuthorities.contains(authority)) {
|
||||
LOG.debug("Saving authority '{}' in local database", authority);
|
||||
Authority authorityToSave = new Authority();
|
||||
authorityToSave.setName(authority);
|
||||
authorityRepository.save(authorityToSave);
|
||||
}
|
||||
}
|
||||
// save account in to sync users between IdP and JHipster's local database
|
||||
Optional<User> existingUser = userRepository.findOneByLogin(user.getLogin());
|
||||
if (existingUser.isPresent()) {
|
||||
// if IdP sends last updated information, use it to determine if an update should happen
|
||||
if (details.get("updated_at") != null) {
|
||||
Instant dbModifiedDate = existingUser.orElseThrow().getLastModifiedDate();
|
||||
Instant idpModifiedDate;
|
||||
if (details.get("updated_at") instanceof Instant) {
|
||||
idpModifiedDate = (Instant) details.get("updated_at");
|
||||
} else {
|
||||
idpModifiedDate = Instant.ofEpochSecond((Integer) details.get("updated_at"));
|
||||
}
|
||||
if (idpModifiedDate.isAfter(dbModifiedDate)) {
|
||||
LOG.debug("Updating user '{}' in local database", user.getLogin());
|
||||
updateUser(user.getFirstName(), user.getLastName(), user.getEmail(), user.getLangKey(), user.getImageUrl());
|
||||
}
|
||||
// no last updated info, blindly update
|
||||
} else {
|
||||
LOG.debug("Updating user '{}' in local database", user.getLogin());
|
||||
updateUser(user.getFirstName(), user.getLastName(), user.getEmail(), user.getLangKey(), user.getImageUrl());
|
||||
}
|
||||
} else {
|
||||
LOG.debug("Saving user '{}' in local database", user.getLogin());
|
||||
userRepository.save(user);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user from an OAuth 2.0 login or resource server with JWT.
|
||||
* Synchronizes the user in the local repository.
|
||||
*
|
||||
* @param authToken the authentication token.
|
||||
* @return the user from the authentication.
|
||||
*/
|
||||
@Transactional
|
||||
public AdminUserDTO getUserFromAuthentication(AbstractAuthenticationToken authToken) {
|
||||
Map<String, Object> attributes;
|
||||
if (authToken instanceof OAuth2AuthenticationToken) {
|
||||
attributes = ((OAuth2AuthenticationToken) authToken).getPrincipal().getAttributes();
|
||||
} else if (authToken instanceof JwtAuthenticationToken) {
|
||||
attributes = ((JwtAuthenticationToken) authToken).getTokenAttributes();
|
||||
} else {
|
||||
throw new IllegalArgumentException("AuthenticationToken is not OAuth2 or JWT!");
|
||||
}
|
||||
User user = getUser(attributes);
|
||||
user.setAuthorities(
|
||||
authToken
|
||||
.getAuthorities()
|
||||
.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.map(authority -> {
|
||||
Authority auth = new Authority();
|
||||
auth.setName(authority);
|
||||
return auth;
|
||||
})
|
||||
.collect(Collectors.toSet())
|
||||
);
|
||||
|
||||
return new AdminUserDTO(syncUserWithIdP(attributes, user));
|
||||
}
|
||||
|
||||
private static User getUser(Map<String, Object> details) {
|
||||
User user = new User();
|
||||
Boolean activated = Boolean.TRUE;
|
||||
String sub = String.valueOf(details.get("sub"));
|
||||
String username = null;
|
||||
if (details.get("preferred_username") != null) {
|
||||
username = ((String) details.get("preferred_username")).toLowerCase();
|
||||
}
|
||||
// handle resource server JWT, where sub claim is email and uid is ID
|
||||
if (details.get("uid") != null) {
|
||||
user.setId((String) details.get("uid"));
|
||||
user.setLogin(sub);
|
||||
} else {
|
||||
user.setId(sub);
|
||||
}
|
||||
if (username != null) {
|
||||
user.setLogin(username);
|
||||
} else if (user.getLogin() == null) {
|
||||
user.setLogin(user.getId());
|
||||
}
|
||||
if (details.get("given_name") != null) {
|
||||
user.setFirstName((String) details.get("given_name"));
|
||||
} else if (details.get("name") != null) {
|
||||
user.setFirstName((String) details.get("name"));
|
||||
}
|
||||
if (details.get("family_name") != null) {
|
||||
user.setLastName((String) details.get("family_name"));
|
||||
}
|
||||
if (details.get("email_verified") != null) {
|
||||
activated = (Boolean) details.get("email_verified");
|
||||
}
|
||||
if (details.get("email") != null) {
|
||||
user.setEmail(((String) details.get("email")).toLowerCase());
|
||||
} else if (sub.contains("|") && (username != null && username.contains("@"))) {
|
||||
// special handling for Auth0
|
||||
user.setEmail(username);
|
||||
} else {
|
||||
user.setEmail(sub);
|
||||
}
|
||||
if (details.get("langKey") != null) {
|
||||
user.setLangKey((String) details.get("langKey"));
|
||||
} else if (details.get("locale") != null) {
|
||||
// trim off country code if it exists
|
||||
String locale = (String) details.get("locale");
|
||||
if (locale.contains("_")) {
|
||||
locale = locale.substring(0, locale.indexOf('_'));
|
||||
} else if (locale.contains("-")) {
|
||||
locale = locale.substring(0, locale.indexOf('-'));
|
||||
}
|
||||
user.setLangKey(locale.toLowerCase());
|
||||
} else {
|
||||
// set langKey to default if not specified by IdP
|
||||
user.setLangKey(Constants.DEFAULT_LANGUAGE);
|
||||
}
|
||||
if (details.get("picture") != null) {
|
||||
user.setImageUrl((String) details.get("picture"));
|
||||
}
|
||||
user.setActivated(activated);
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.sasiedzi.event.service.dto;
|
||||
|
||||
import com.sasiedzi.event.config.Constants;
|
||||
import com.sasiedzi.event.domain.Authority;
|
||||
import com.sasiedzi.event.domain.User;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A DTO representing a user, with his authorities.
|
||||
*/
|
||||
public class AdminUserDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String id;
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = Constants.LOGIN_REGEX)
|
||||
@Size(min = 1, max = 50)
|
||||
private String login;
|
||||
|
||||
@Size(max = 50)
|
||||
private String firstName;
|
||||
|
||||
@Size(max = 50)
|
||||
private String lastName;
|
||||
|
||||
@Email
|
||||
@Size(min = 5, max = 254)
|
||||
private String email;
|
||||
|
||||
@Size(max = 256)
|
||||
private String imageUrl;
|
||||
|
||||
private boolean activated = false;
|
||||
|
||||
@Size(min = 2, max = 10)
|
||||
private String langKey;
|
||||
|
||||
private String createdBy;
|
||||
|
||||
private Instant createdDate;
|
||||
|
||||
private String lastModifiedBy;
|
||||
|
||||
private Instant lastModifiedDate;
|
||||
|
||||
private Set<String> authorities;
|
||||
|
||||
public AdminUserDTO() {
|
||||
// Empty constructor needed for Jackson.
|
||||
}
|
||||
|
||||
public AdminUserDTO(User user) {
|
||||
this.id = user.getId();
|
||||
this.login = user.getLogin();
|
||||
this.firstName = user.getFirstName();
|
||||
this.lastName = user.getLastName();
|
||||
this.email = user.getEmail();
|
||||
this.activated = user.isActivated();
|
||||
this.imageUrl = user.getImageUrl();
|
||||
this.langKey = user.getLangKey();
|
||||
this.createdBy = user.getCreatedBy();
|
||||
this.createdDate = user.getCreatedDate();
|
||||
this.lastModifiedBy = user.getLastModifiedBy();
|
||||
this.lastModifiedDate = user.getLastModifiedDate();
|
||||
this.authorities = user.getAuthorities().stream().map(Authority::getName).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getImageUrl() {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
public void setImageUrl(String imageUrl) {
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
|
||||
public boolean isActivated() {
|
||||
return activated;
|
||||
}
|
||||
|
||||
public void setActivated(boolean activated) {
|
||||
this.activated = activated;
|
||||
}
|
||||
|
||||
public String getLangKey() {
|
||||
return langKey;
|
||||
}
|
||||
|
||||
public void setLangKey(String langKey) {
|
||||
this.langKey = langKey;
|
||||
}
|
||||
|
||||
public String getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public void setCreatedBy(String createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
|
||||
public Instant getCreatedDate() {
|
||||
return createdDate;
|
||||
}
|
||||
|
||||
public void setCreatedDate(Instant createdDate) {
|
||||
this.createdDate = createdDate;
|
||||
}
|
||||
|
||||
public String getLastModifiedBy() {
|
||||
return lastModifiedBy;
|
||||
}
|
||||
|
||||
public void setLastModifiedBy(String lastModifiedBy) {
|
||||
this.lastModifiedBy = lastModifiedBy;
|
||||
}
|
||||
|
||||
public Instant getLastModifiedDate() {
|
||||
return lastModifiedDate;
|
||||
}
|
||||
|
||||
public void setLastModifiedDate(Instant lastModifiedDate) {
|
||||
this.lastModifiedDate = lastModifiedDate;
|
||||
}
|
||||
|
||||
public Set<String> getAuthorities() {
|
||||
return authorities;
|
||||
}
|
||||
|
||||
public void setAuthorities(Set<String> authorities) {
|
||||
this.authorities = authorities;
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AdminUserDTO{" +
|
||||
"login='" + login + '\'' +
|
||||
", firstName='" + firstName + '\'' +
|
||||
", lastName='" + lastName + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", imageUrl='" + imageUrl + '\'' +
|
||||
", activated=" + activated +
|
||||
", langKey='" + langKey + '\'' +
|
||||
", createdBy=" + createdBy +
|
||||
", createdDate=" + createdDate +
|
||||
", lastModifiedBy='" + lastModifiedBy + '\'' +
|
||||
", lastModifiedDate=" + lastModifiedDate +
|
||||
", authorities=" + authorities +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.sasiedzi.event.service.dto;
|
||||
|
||||
import com.sasiedzi.event.domain.User;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A DTO representing a user, with only the public attributes.
|
||||
*/
|
||||
public class UserDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String id;
|
||||
|
||||
private String login;
|
||||
|
||||
public UserDTO() {
|
||||
// Empty constructor needed for Jackson.
|
||||
}
|
||||
|
||||
public UserDTO(User user) {
|
||||
this.id = user.getId();
|
||||
// Customize it here if you need, or not, firstName/lastName/etc
|
||||
this.login = user.getLogin();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UserDTO userDTO = (UserDTO) o;
|
||||
if (userDTO.getId() == null || getId() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(getId(), userDTO.getId()) && Objects.equals(getLogin(), userDTO.getLogin());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getId(), getLogin());
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserDTO{" +
|
||||
"id='" + id + '\'' +
|
||||
", login='" + login + '\'' +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Data transfer objects for rest mapping.
|
||||
*/
|
||||
package com.sasiedzi.event.service.dto;
|
||||
@@ -0,0 +1,150 @@
|
||||
package com.sasiedzi.event.service.mapper;
|
||||
|
||||
import com.sasiedzi.event.domain.Authority;
|
||||
import com.sasiedzi.event.domain.User;
|
||||
import com.sasiedzi.event.service.dto.AdminUserDTO;
|
||||
import com.sasiedzi.event.service.dto.UserDTO;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Mapper for the entity {@link User} and its DTO called {@link UserDTO}.
|
||||
*
|
||||
* Normal mappers are generated using MapStruct, this one is hand-coded as MapStruct
|
||||
* support is still in beta, and requires a manual step with an IDE.
|
||||
*/
|
||||
@Service
|
||||
public class UserMapper {
|
||||
|
||||
public List<UserDTO> usersToUserDTOs(List<User> users) {
|
||||
return users.stream().filter(Objects::nonNull).map(this::userToUserDTO).toList();
|
||||
}
|
||||
|
||||
public UserDTO userToUserDTO(User user) {
|
||||
return new UserDTO(user);
|
||||
}
|
||||
|
||||
public List<AdminUserDTO> usersToAdminUserDTOs(List<User> users) {
|
||||
return users.stream().filter(Objects::nonNull).map(this::userToAdminUserDTO).toList();
|
||||
}
|
||||
|
||||
public AdminUserDTO userToAdminUserDTO(User user) {
|
||||
return new AdminUserDTO(user);
|
||||
}
|
||||
|
||||
public List<User> userDTOsToUsers(List<AdminUserDTO> userDTOs) {
|
||||
return userDTOs.stream().filter(Objects::nonNull).map(this::userDTOToUser).toList();
|
||||
}
|
||||
|
||||
public User userDTOToUser(AdminUserDTO userDTO) {
|
||||
if (userDTO == null) {
|
||||
return null;
|
||||
} else {
|
||||
User user = new User();
|
||||
user.setId(userDTO.getId());
|
||||
user.setLogin(userDTO.getLogin());
|
||||
user.setFirstName(userDTO.getFirstName());
|
||||
user.setLastName(userDTO.getLastName());
|
||||
user.setEmail(userDTO.getEmail());
|
||||
user.setImageUrl(userDTO.getImageUrl());
|
||||
user.setCreatedBy(userDTO.getCreatedBy());
|
||||
user.setCreatedDate(userDTO.getCreatedDate());
|
||||
user.setLastModifiedBy(userDTO.getLastModifiedBy());
|
||||
user.setLastModifiedDate(userDTO.getLastModifiedDate());
|
||||
user.setActivated(userDTO.isActivated());
|
||||
user.setLangKey(userDTO.getLangKey());
|
||||
Set<Authority> authorities = this.authoritiesFromStrings(userDTO.getAuthorities());
|
||||
user.setAuthorities(authorities);
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Authority> authoritiesFromStrings(Set<String> authoritiesAsString) {
|
||||
Set<Authority> authorities = new HashSet<>();
|
||||
|
||||
if (authoritiesAsString != null) {
|
||||
authorities = authoritiesAsString
|
||||
.stream()
|
||||
.map(string -> {
|
||||
Authority auth = new Authority();
|
||||
auth.setName(string);
|
||||
return auth;
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
return authorities;
|
||||
}
|
||||
|
||||
public User userFromId(String id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
User user = new User();
|
||||
user.setId(id);
|
||||
return user;
|
||||
}
|
||||
|
||||
@Named("id")
|
||||
@BeanMapping(ignoreByDefault = true)
|
||||
@Mapping(target = "id", source = "id")
|
||||
public UserDTO toDtoId(User user) {
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
UserDTO userDto = new UserDTO();
|
||||
userDto.setId(user.getId());
|
||||
return userDto;
|
||||
}
|
||||
|
||||
@Named("idSet")
|
||||
@BeanMapping(ignoreByDefault = true)
|
||||
@Mapping(target = "id", source = "id")
|
||||
public Set<UserDTO> toDtoIdSet(Set<User> users) {
|
||||
if (users == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
Set<UserDTO> userSet = new HashSet<>();
|
||||
for (User userEntity : users) {
|
||||
userSet.add(this.toDtoId(userEntity));
|
||||
}
|
||||
|
||||
return userSet;
|
||||
}
|
||||
|
||||
@Named("login")
|
||||
@BeanMapping(ignoreByDefault = true)
|
||||
@Mapping(target = "id", source = "id")
|
||||
@Mapping(target = "login", source = "login")
|
||||
public UserDTO toDtoLogin(User user) {
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
UserDTO userDto = new UserDTO();
|
||||
userDto.setId(user.getId());
|
||||
userDto.setLogin(user.getLogin());
|
||||
return userDto;
|
||||
}
|
||||
|
||||
@Named("loginSet")
|
||||
@BeanMapping(ignoreByDefault = true)
|
||||
@Mapping(target = "id", source = "id")
|
||||
@Mapping(target = "login", source = "login")
|
||||
public Set<UserDTO> toDtoLoginSet(Set<User> users) {
|
||||
if (users == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
Set<UserDTO> userSet = new HashSet<>();
|
||||
for (User userEntity : users) {
|
||||
userSet.add(this.toDtoLogin(userEntity));
|
||||
}
|
||||
|
||||
return userSet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Data transfer objects mappers.
|
||||
*/
|
||||
package com.sasiedzi.event.service.mapper;
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Service layer.
|
||||
*/
|
||||
package com.sasiedzi.event.service;
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.sasiedzi.event.web.filter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
/**
|
||||
* Refresh oauth2 tokens.
|
||||
*/
|
||||
@Component
|
||||
public class OAuth2RefreshTokensWebFilter extends OncePerRequestFilter {
|
||||
|
||||
private final OAuth2AuthorizedClientManager clientManager;
|
||||
private final OAuth2AuthorizedClientRepository authorizedClientRepository;
|
||||
private final OAuth2AuthorizationRequestResolver authorizationRequestResolver;
|
||||
private final RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy();
|
||||
|
||||
public OAuth2RefreshTokensWebFilter(
|
||||
OAuth2AuthorizedClientManager clientManager,
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository,
|
||||
ClientRegistrationRepository clientRegistrationRepository
|
||||
) {
|
||||
this.clientManager = clientManager;
|
||||
this.authorizedClientRepository = authorizedClientRepository;
|
||||
this.authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(
|
||||
clientRegistrationRepository,
|
||||
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if ((authentication instanceof OAuth2AuthenticationToken)) {
|
||||
try {
|
||||
OAuth2AuthorizedClient authorizedClient = authorizedClient((OAuth2AuthenticationToken) authentication);
|
||||
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, authentication, request, response);
|
||||
} catch (Exception e) {
|
||||
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
|
||||
if (authorizationRequest != null) {
|
||||
this.authorizationRedirectStrategy.sendRedirect(request, response, authorizationRequest.getAuthorizationRequestUri());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClient authorizedClient(OAuth2AuthenticationToken oauth2Authentication) {
|
||||
String clientRegistrationId = oauth2Authentication.getAuthorizedClientRegistrationId();
|
||||
OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId)
|
||||
.principal(oauth2Authentication)
|
||||
.build();
|
||||
if (clientManager == null) {
|
||||
throw new IllegalStateException(
|
||||
"No OAuth2AuthorizedClientManager bean was found. Did you include the " +
|
||||
"org.springframework.boot:spring-boot-starter-oauth2-client dependency?"
|
||||
);
|
||||
}
|
||||
return clientManager.authorize(request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.sasiedzi.event.web.filter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
public class SpaWebFilter extends OncePerRequestFilter {
|
||||
|
||||
/**
|
||||
* Forwards any unmapped paths (except those containing a period) to the client {@code index.html}.
|
||||
*/
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
// Request URI includes the contextPath if any, removed it.
|
||||
String path = request.getRequestURI().substring(request.getContextPath().length());
|
||||
if (
|
||||
!path.startsWith("/api") &&
|
||||
!path.startsWith("/management") &&
|
||||
!path.startsWith("/v3/api-docs") &&
|
||||
!path.startsWith("/login") &&
|
||||
!path.startsWith("/oauth2") &&
|
||||
!path.contains(".") &&
|
||||
path.matches("/(.*)")
|
||||
) {
|
||||
request.getRequestDispatcher("/index.html").forward(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Request chain filters.
|
||||
*/
|
||||
package com.sasiedzi.event.web.filter;
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.sasiedzi.event.web.rest;
|
||||
|
||||
import com.sasiedzi.event.service.UserService;
|
||||
import com.sasiedzi.event.service.dto.AdminUserDTO;
|
||||
import java.security.Principal;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* REST controller for managing the current user's account.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class AccountResource {
|
||||
|
||||
private static class AccountResourceException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private AccountResourceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AccountResource.class);
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public AccountResource(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /account} : get the current user.
|
||||
*
|
||||
* @param principal the current user; resolves to {@code null} if not authenticated.
|
||||
* @return the current user.
|
||||
* @throws AccountResourceException {@code 500 (Internal Server Error)} if the user couldn't be returned.
|
||||
*/
|
||||
@GetMapping("/account")
|
||||
public AdminUserDTO getAccount(Principal principal) {
|
||||
if (principal instanceof AbstractAuthenticationToken) {
|
||||
return userService.getUserFromAuthentication((AbstractAuthenticationToken) principal);
|
||||
} else {
|
||||
throw new AccountResourceException("User could not be found");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /authenticate} : check if the user is authenticated, and return its login.
|
||||
*
|
||||
* @param principal the authentication principal.
|
||||
* @return the login if the user is authenticated.
|
||||
*/
|
||||
@GetMapping(value = "/authenticate", produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
public String isAuthenticated(Principal principal) {
|
||||
LOG.debug("REST request to check if the current user is authenticated");
|
||||
return principal == null ? null : principal.getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.sasiedzi.event.web.rest;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Resource to return information about OIDC properties
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class AuthInfoResource {
|
||||
|
||||
@Value("${spring.security.oauth2.client.provider.oidc.issuer-uri:}")
|
||||
private String issuer;
|
||||
|
||||
@Value("${spring.security.oauth2.client.registration.oidc.client-id:}")
|
||||
private String clientId;
|
||||
|
||||
@GetMapping("/auth-info")
|
||||
public AuthInfoVM getAuthInfo() {
|
||||
return new AuthInfoVM(issuer, clientId);
|
||||
}
|
||||
|
||||
class AuthInfoVM {
|
||||
|
||||
private String issuer;
|
||||
private String clientId;
|
||||
|
||||
AuthInfoVM(String issuer, String clientId) {
|
||||
this.issuer = issuer;
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getIssuer() {
|
||||
return this.issuer;
|
||||
}
|
||||
|
||||
public void setIssuer(String issuer) {
|
||||
this.issuer = issuer;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.sasiedzi.event.web.rest;
|
||||
|
||||
import com.sasiedzi.event.domain.Authority;
|
||||
import com.sasiedzi.event.repository.AuthorityRepository;
|
||||
import com.sasiedzi.event.web.rest.errors.BadRequestAlertException;
|
||||
import jakarta.validation.Valid;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import tech.jhipster.web.util.HeaderUtil;
|
||||
import tech.jhipster.web.util.ResponseUtil;
|
||||
|
||||
/**
|
||||
* REST controller for managing {@link com.sasiedzi.event.domain.Authority}.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/authorities")
|
||||
@Transactional
|
||||
public class AuthorityResource {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AuthorityResource.class);
|
||||
|
||||
private static final String ENTITY_NAME = "adminAuthority";
|
||||
|
||||
@Value("${jhipster.clientApp.name}")
|
||||
private String applicationName;
|
||||
|
||||
private final AuthorityRepository authorityRepository;
|
||||
|
||||
public AuthorityResource(AuthorityRepository authorityRepository) {
|
||||
this.authorityRepository = authorityRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code POST /authorities} : Create a new authority.
|
||||
*
|
||||
* @param authority the authority to create.
|
||||
* @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new authority, or with status {@code 400 (Bad Request)} if the authority has already an ID.
|
||||
* @throws URISyntaxException if the Location URI syntax is incorrect.
|
||||
*/
|
||||
@PostMapping("")
|
||||
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
|
||||
public ResponseEntity<Authority> createAuthority(@Valid @RequestBody Authority authority) throws URISyntaxException {
|
||||
LOG.debug("REST request to save Authority : {}", authority);
|
||||
if (authorityRepository.existsById(authority.getName())) {
|
||||
throw new BadRequestAlertException("authority already exists", ENTITY_NAME, "idexists");
|
||||
}
|
||||
authority = authorityRepository.save(authority);
|
||||
return ResponseEntity.created(new URI("/api/authorities/" + authority.getName()))
|
||||
.headers(HeaderUtil.createEntityCreationAlert(applicationName, false, ENTITY_NAME, authority.getName()))
|
||||
.body(authority);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /authorities} : get all the authorities.
|
||||
*
|
||||
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of authorities in body.
|
||||
*/
|
||||
@GetMapping("")
|
||||
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
|
||||
public List<Authority> getAllAuthorities() {
|
||||
LOG.debug("REST request to get all Authorities");
|
||||
return authorityRepository.findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /authorities/:id} : get the "id" authority.
|
||||
*
|
||||
* @param id the id of the authority to retrieve.
|
||||
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the authority, or with status {@code 404 (Not Found)}.
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
|
||||
public ResponseEntity<Authority> getAuthority(@PathVariable("id") String id) {
|
||||
LOG.debug("REST request to get Authority : {}", id);
|
||||
Optional<Authority> authority = authorityRepository.findById(id);
|
||||
return ResponseUtil.wrapOrNotFound(authority);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code DELETE /authorities/:id} : delete the "id" authority.
|
||||
*
|
||||
* @param id the id of the authority to delete.
|
||||
* @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}.
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
|
||||
public ResponseEntity<Void> deleteAuthority(@PathVariable("id") String id) {
|
||||
LOG.debug("REST request to delete Authority : {}", id);
|
||||
authorityRepository.deleteById(id);
|
||||
return ResponseEntity.noContent().headers(HeaderUtil.createEntityDeletionAlert(applicationName, false, ENTITY_NAME, id)).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.sasiedzi.event.web.rest;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* REST controller for managing global OIDC logout.
|
||||
*/
|
||||
@RestController
|
||||
public class LogoutResource {
|
||||
|
||||
private final ClientRegistration registration;
|
||||
|
||||
public LogoutResource(ClientRegistrationRepository registrations) {
|
||||
this.registration = registrations.findByRegistrationId("oidc");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code POST /api/logout} : logout the current user.
|
||||
*
|
||||
* @param request the {@link HttpServletRequest}.
|
||||
* @param idToken the ID token.
|
||||
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and a body with a global logout URL.
|
||||
*/
|
||||
@PostMapping("/api/logout")
|
||||
public ResponseEntity<?> logout(HttpServletRequest request, @AuthenticationPrincipal(expression = "idToken") OidcIdToken idToken) {
|
||||
StringBuilder logoutUrl = new StringBuilder();
|
||||
|
||||
logoutUrl.append(this.registration.getProviderDetails().getConfigurationMetadata().get("end_session_endpoint").toString());
|
||||
|
||||
String originUrl = request.getHeader(HttpHeaders.ORIGIN);
|
||||
|
||||
logoutUrl.append("?id_token_hint=").append(idToken.getTokenValue()).append("&post_logout_redirect_uri=").append(originUrl);
|
||||
|
||||
request.getSession().invalidate();
|
||||
return ResponseEntity.ok().body(Map.of("logoutUrl", logoutUrl.toString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.sasiedzi.event.web.rest;
|
||||
|
||||
import com.sasiedzi.event.service.UserService;
|
||||
import com.sasiedzi.event.service.dto.UserDTO;
|
||||
import java.util.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import tech.jhipster.web.util.PaginationUtil;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class PublicUserResource {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PublicUserResource.class);
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public PublicUserResource(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET /users} : get all users with only public information - calling this method is allowed for anyone.
|
||||
*
|
||||
* @param pageable the pagination information.
|
||||
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users.
|
||||
*/
|
||||
@GetMapping("/users")
|
||||
public ResponseEntity<List<UserDTO>> getAllPublicUsers(@org.springdoc.core.annotations.ParameterObject Pageable pageable) {
|
||||
LOG.debug("REST request to get all public User names");
|
||||
|
||||
final Page<UserDTO> page = userService.getAllPublicUsers(pageable);
|
||||
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page);
|
||||
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.sasiedzi.event.web.rest.errors;
|
||||
|
||||
import java.net.URI;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
import tech.jhipster.web.rest.errors.ProblemDetailWithCause;
|
||||
import tech.jhipster.web.rest.errors.ProblemDetailWithCause.ProblemDetailWithCauseBuilder;
|
||||
|
||||
@SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep
|
||||
public class BadRequestAlertException extends ErrorResponseException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String entityName;
|
||||
|
||||
private final String errorKey;
|
||||
|
||||
public BadRequestAlertException(String defaultMessage, String entityName, String errorKey) {
|
||||
this(ErrorConstants.DEFAULT_TYPE, defaultMessage, entityName, errorKey);
|
||||
}
|
||||
|
||||
public BadRequestAlertException(URI type, String defaultMessage, String entityName, String errorKey) {
|
||||
super(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
ProblemDetailWithCauseBuilder.instance()
|
||||
.withStatus(HttpStatus.BAD_REQUEST.value())
|
||||
.withType(type)
|
||||
.withTitle(defaultMessage)
|
||||
.withProperty("message", "error." + errorKey)
|
||||
.withProperty("params", entityName)
|
||||
.build(),
|
||||
null
|
||||
);
|
||||
this.entityName = entityName;
|
||||
this.errorKey = errorKey;
|
||||
}
|
||||
|
||||
public String getEntityName() {
|
||||
return entityName;
|
||||
}
|
||||
|
||||
public String getErrorKey() {
|
||||
return errorKey;
|
||||
}
|
||||
|
||||
public ProblemDetailWithCause getProblemDetailWithCause() {
|
||||
return (ProblemDetailWithCause) this.getBody();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.sasiedzi.event.web.rest.errors;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public final class ErrorConstants {
|
||||
|
||||
public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure";
|
||||
public static final String ERR_VALIDATION = "error.validation";
|
||||
public static final String PROBLEM_BASE_URL = "https://www.jhipster.tech/problem";
|
||||
public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message");
|
||||
public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation");
|
||||
|
||||
private ErrorConstants() {}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package com.sasiedzi.event.web.rest.errors;
|
||||
|
||||
import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageConversionException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.web.ErrorResponse;
|
||||
import org.springframework.web.ErrorResponseException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
import tech.jhipster.web.rest.errors.ProblemDetailWithCause;
|
||||
import tech.jhipster.web.rest.errors.ProblemDetailWithCause.ProblemDetailWithCauseBuilder;
|
||||
import tech.jhipster.web.util.HeaderUtil;
|
||||
|
||||
/**
|
||||
* Controller advice to translate the server side exceptions to client-friendly json structures.
|
||||
* The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807).
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class ExceptionTranslator extends ResponseEntityExceptionHandler {
|
||||
|
||||
private static final String FIELD_ERRORS_KEY = "fieldErrors";
|
||||
private static final String MESSAGE_KEY = "message";
|
||||
private static final String PATH_KEY = "path";
|
||||
private static final boolean CASUAL_CHAIN_ENABLED = false;
|
||||
|
||||
@Value("${jhipster.clientApp.name}")
|
||||
private String applicationName;
|
||||
|
||||
private final Environment env;
|
||||
|
||||
public ExceptionTranslator(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
@ExceptionHandler
|
||||
public ResponseEntity<Object> handleAnyException(Throwable ex, NativeWebRequest request) {
|
||||
ProblemDetailWithCause pdCause = wrapAndCustomizeProblem(ex, request);
|
||||
return handleExceptionInternal((Exception) ex, pdCause, buildHeaders(ex), HttpStatusCode.valueOf(pdCause.getStatus()), request);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected ResponseEntity<Object> handleExceptionInternal(
|
||||
Exception ex,
|
||||
@Nullable Object body,
|
||||
HttpHeaders headers,
|
||||
HttpStatusCode statusCode,
|
||||
WebRequest request
|
||||
) {
|
||||
body = body == null ? wrapAndCustomizeProblem((Throwable) ex, (NativeWebRequest) request) : body;
|
||||
return super.handleExceptionInternal(ex, body, headers, statusCode, request);
|
||||
}
|
||||
|
||||
protected ProblemDetailWithCause wrapAndCustomizeProblem(Throwable ex, NativeWebRequest request) {
|
||||
return customizeProblem(getProblemDetailWithCause(ex), ex, request);
|
||||
}
|
||||
|
||||
private ProblemDetailWithCause getProblemDetailWithCause(Throwable ex) {
|
||||
if (
|
||||
ex instanceof ErrorResponseException exp && exp.getBody() instanceof ProblemDetailWithCause problemDetailWithCause
|
||||
) return problemDetailWithCause;
|
||||
return ProblemDetailWithCauseBuilder.instance().withStatus(toStatus(ex).value()).build();
|
||||
}
|
||||
|
||||
protected ProblemDetailWithCause customizeProblem(ProblemDetailWithCause problem, Throwable err, NativeWebRequest request) {
|
||||
if (problem.getStatus() <= 0) problem.setStatus(toStatus(err));
|
||||
|
||||
if (problem.getType() == null || problem.getType().equals(URI.create("about:blank"))) problem.setType(getMappedType(err));
|
||||
|
||||
// higher precedence to Custom/ResponseStatus types
|
||||
String title = extractTitle(err, problem.getStatus());
|
||||
String problemTitle = problem.getTitle();
|
||||
if (problemTitle == null || !problemTitle.equals(title)) {
|
||||
problem.setTitle(title);
|
||||
}
|
||||
|
||||
if (problem.getDetail() == null) {
|
||||
// higher precedence to cause
|
||||
problem.setDetail(getCustomizedErrorDetails(err));
|
||||
}
|
||||
|
||||
Map<String, Object> problemProperties = problem.getProperties();
|
||||
if (problemProperties == null || !problemProperties.containsKey(MESSAGE_KEY)) problem.setProperty(
|
||||
MESSAGE_KEY,
|
||||
getMappedMessageKey(err) != null ? getMappedMessageKey(err) : "error.http." + problem.getStatus()
|
||||
);
|
||||
|
||||
if (problemProperties == null || !problemProperties.containsKey(PATH_KEY)) problem.setProperty(PATH_KEY, getPathValue(request));
|
||||
|
||||
if (
|
||||
(err instanceof MethodArgumentNotValidException fieldException) &&
|
||||
(problemProperties == null || !problemProperties.containsKey(FIELD_ERRORS_KEY))
|
||||
) problem.setProperty(FIELD_ERRORS_KEY, getFieldErrors(fieldException));
|
||||
|
||||
problem.setCause(buildCause(err.getCause(), request).orElse(null));
|
||||
|
||||
return problem;
|
||||
}
|
||||
|
||||
private String extractTitle(Throwable err, int statusCode) {
|
||||
return getCustomizedTitle(err) != null ? getCustomizedTitle(err) : extractTitleForResponseStatus(err, statusCode);
|
||||
}
|
||||
|
||||
private List<FieldErrorVM> getFieldErrors(MethodArgumentNotValidException ex) {
|
||||
return ex
|
||||
.getBindingResult()
|
||||
.getFieldErrors()
|
||||
.stream()
|
||||
.map(f ->
|
||||
new FieldErrorVM(
|
||||
f.getObjectName().replaceFirst("DTO$", ""),
|
||||
f.getField(),
|
||||
StringUtils.isNotBlank(f.getDefaultMessage()) ? f.getDefaultMessage() : f.getCode()
|
||||
)
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private String extractTitleForResponseStatus(Throwable err, int statusCode) {
|
||||
ResponseStatus specialStatus = extractResponseStatus(err);
|
||||
return specialStatus == null ? HttpStatus.valueOf(statusCode).getReasonPhrase() : specialStatus.reason();
|
||||
}
|
||||
|
||||
private String extractURI(NativeWebRequest request) {
|
||||
HttpServletRequest nativeRequest = request.getNativeRequest(HttpServletRequest.class);
|
||||
return nativeRequest != null ? nativeRequest.getRequestURI() : StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
private HttpStatus toStatus(final Throwable throwable) {
|
||||
// Let the ErrorResponse take this responsibility
|
||||
if (throwable instanceof ErrorResponse err) return HttpStatus.valueOf(err.getBody().getStatus());
|
||||
|
||||
return Optional.ofNullable(getMappedStatus(throwable)).orElse(
|
||||
Optional.ofNullable(resolveResponseStatus(throwable)).map(ResponseStatus::value).orElse(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
);
|
||||
}
|
||||
|
||||
private ResponseStatus extractResponseStatus(final Throwable throwable) {
|
||||
return Optional.ofNullable(resolveResponseStatus(throwable)).orElse(null);
|
||||
}
|
||||
|
||||
private ResponseStatus resolveResponseStatus(final Throwable type) {
|
||||
final ResponseStatus candidate = findMergedAnnotation(type.getClass(), ResponseStatus.class);
|
||||
return candidate == null && type.getCause() != null ? resolveResponseStatus(type.getCause()) : candidate;
|
||||
}
|
||||
|
||||
private URI getMappedType(Throwable err) {
|
||||
if (err instanceof MethodArgumentNotValidException) return ErrorConstants.CONSTRAINT_VIOLATION_TYPE;
|
||||
return ErrorConstants.DEFAULT_TYPE;
|
||||
}
|
||||
|
||||
private String getMappedMessageKey(Throwable err) {
|
||||
if (err instanceof MethodArgumentNotValidException) {
|
||||
return ErrorConstants.ERR_VALIDATION;
|
||||
} else if (err instanceof ConcurrencyFailureException || err.getCause() instanceof ConcurrencyFailureException) {
|
||||
return ErrorConstants.ERR_CONCURRENCY_FAILURE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getCustomizedTitle(Throwable err) {
|
||||
if (err instanceof MethodArgumentNotValidException) return "Method argument not valid";
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getCustomizedErrorDetails(Throwable err) {
|
||||
Collection<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
|
||||
if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) {
|
||||
if (err instanceof HttpMessageConversionException) return "Unable to convert http message";
|
||||
if (err instanceof DataAccessException) return "Failure during data access";
|
||||
if (containsPackageName(err.getMessage())) return "Unexpected runtime exception";
|
||||
}
|
||||
return err.getCause() != null ? err.getCause().getMessage() : err.getMessage();
|
||||
}
|
||||
|
||||
private HttpStatus getMappedStatus(Throwable err) {
|
||||
// Where we disagree with Spring defaults
|
||||
if (err instanceof AccessDeniedException) return HttpStatus.FORBIDDEN;
|
||||
if (err instanceof ConcurrencyFailureException) return HttpStatus.CONFLICT;
|
||||
if (err instanceof BadCredentialsException) return HttpStatus.UNAUTHORIZED;
|
||||
return null;
|
||||
}
|
||||
|
||||
private URI getPathValue(NativeWebRequest request) {
|
||||
if (request == null) return URI.create("about:blank");
|
||||
return URI.create(extractURI(request));
|
||||
}
|
||||
|
||||
private HttpHeaders buildHeaders(Throwable err) {
|
||||
return err instanceof BadRequestAlertException badRequestAlertException
|
||||
? HeaderUtil.createFailureAlert(
|
||||
applicationName,
|
||||
true,
|
||||
badRequestAlertException.getEntityName(),
|
||||
badRequestAlertException.getErrorKey(),
|
||||
badRequestAlertException.getMessage()
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
public Optional<ProblemDetailWithCause> buildCause(final Throwable throwable, NativeWebRequest request) {
|
||||
if (throwable != null && isCasualChainEnabled()) {
|
||||
return Optional.of(customizeProblem(getProblemDetailWithCause(throwable), throwable, request));
|
||||
}
|
||||
return Optional.ofNullable(null);
|
||||
}
|
||||
|
||||
private boolean isCasualChainEnabled() {
|
||||
// Customize as per the needs
|
||||
return CASUAL_CHAIN_ENABLED;
|
||||
}
|
||||
|
||||
private boolean containsPackageName(String message) {
|
||||
// This list is for sure not complete
|
||||
return StringUtils.containsAny(message, "org.", "java.", "net.", "jakarta.", "javax.", "com.", "io.", "de.", "com.sasiedzi.event");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.sasiedzi.event.web.rest.errors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class FieldErrorVM implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String objectName;
|
||||
|
||||
private final String field;
|
||||
|
||||
private final String message;
|
||||
|
||||
public FieldErrorVM(String dto, String field, String message) {
|
||||
this.objectName = dto;
|
||||
this.field = field;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getObjectName() {
|
||||
return objectName;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Rest layer error handling.
|
||||
*/
|
||||
package com.sasiedzi.event.web.rest.errors;
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Rest layer.
|
||||
*/
|
||||
package com.sasiedzi.event.web.rest;
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
${AnsiColor.GREEN} ██╗${AnsiColor.BLUE} ██╗ ██╗ ████████╗ ███████╗ ██████╗ ████████╗ ████████╗ ████${AnsiColor.GREEN}███╗
|
||||
${AnsiColor.GREEN} ██║ ██║ ${AnsiColor.BLUE} ██║ ╚══██╔══╝ ██╔═══██╗ ██╔════╝ ╚══██╔══╝ ██╔══${AnsiColor.GREEN}═══╝ ██╔═══██╗
|
||||
${AnsiColor.GREEN} ██║ ████████║${AnsiColor.BLUE} ██║ ███████╔╝ ╚█████╗ ██║ ${AnsiColor.GREEN} ██████╗ ███████╔╝
|
||||
${AnsiColor.GREEN}██╗ ██║ ██╔═══██║ ██║ ${AnsiColor.BLUE} ██╔════╝ ╚═══██╗ ${AnsiColor.GREEN}██║ ██╔═══╝ ██╔══██║
|
||||
${AnsiColor.GREEN}╚██████╔╝ ██║ ██║ ████████╗ ██║ ${AnsiColor.BLUE} ██████${AnsiColor.GREEN}╔╝ ██║ ████████╗ ██║ ╚██╗
|
||||
${AnsiColor.GREEN} ╚═════╝ ╚═╝ ╚═╝ ╚═══════╝ ╚═╝ ${AnsiColor.BLUE} ╚═${AnsiColor.GREEN}════╝ ╚═╝ ╚═══════╝ ╚═╝ ╚═╝
|
||||
|
||||
${AnsiColor.BRIGHT_BLUE}:: JHipster 🤓 :: Running Spring Boot ${spring-boot.version} :: Startup profile(s) ${spring.profiles.active} ::
|
||||
:: https://www.jhipster.tech ::${AnsiColor.DEFAULT}
|
||||
@@ -0,0 +1,88 @@
|
||||
# ===================================================================
|
||||
# Spring Boot configuration for the "dev" profile.
|
||||
#
|
||||
# This configuration overrides the application.yml file.
|
||||
#
|
||||
# More information on profiles: https://www.jhipster.tech/profiles/
|
||||
# More information on configuration properties: https://www.jhipster.tech/common-application-properties/
|
||||
# ===================================================================
|
||||
|
||||
# ===================================================================
|
||||
# Standard Spring Boot properties.
|
||||
# Full reference is available at:
|
||||
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
|
||||
# ===================================================================
|
||||
|
||||
logging:
|
||||
level:
|
||||
ROOT: DEBUG
|
||||
tech.jhipster: DEBUG
|
||||
org.hibernate.SQL: DEBUG
|
||||
com.sasiedzi.event: DEBUG
|
||||
|
||||
spring:
|
||||
devtools:
|
||||
restart:
|
||||
enabled: true
|
||||
additional-exclude: static/**
|
||||
livereload:
|
||||
enabled: false # we use Webpack dev server + BrowserSync for livereload
|
||||
jackson:
|
||||
serialization:
|
||||
indent-output: true
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
url: jdbc:postgresql://localhost:5432/sasiedzi
|
||||
username: sasiedzi
|
||||
password: password
|
||||
hikari:
|
||||
poolName: Hikari
|
||||
auto-commit: false
|
||||
liquibase:
|
||||
# Remove 'faker' if you do not want the sample data to be loaded automatically
|
||||
contexts: dev, faker
|
||||
messages:
|
||||
cache-duration: PT1S # 1 second, see the ISO 8601 standard
|
||||
thymeleaf:
|
||||
cache: false
|
||||
|
||||
server:
|
||||
port: 8080
|
||||
# make sure requests the proxy uri instead of the server one
|
||||
forward-headers-strategy: native
|
||||
|
||||
# ===================================================================
|
||||
# JHipster specific properties
|
||||
#
|
||||
# Full reference is available at: https://www.jhipster.tech/common-application-properties/
|
||||
# ===================================================================
|
||||
|
||||
jhipster:
|
||||
# CORS is only enabled by default with the "dev" profile
|
||||
cors:
|
||||
# Allow Ionic for JHipster by default (* no longer allowed in Spring Boot 2.4+)
|
||||
allowed-origins: 'http://localhost:8100,https://localhost:8100,http://localhost:9000,https://localhost:9000,http://localhost:9060,https://localhost:9060'
|
||||
# Enable CORS when running in GitHub Codespaces
|
||||
allowed-origin-patterns: 'https://*.githubpreview.dev'
|
||||
allowed-methods: '*'
|
||||
allowed-headers: '*'
|
||||
exposed-headers: 'Authorization,Link,X-Total-Count,X-${jhipster.clientApp.name}-alert,X-${jhipster.clientApp.name}-error,X-${jhipster.clientApp.name}-params'
|
||||
allow-credentials: true
|
||||
max-age: 1800
|
||||
logging:
|
||||
use-json-format: false # By default, logs are not in Json format
|
||||
logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration
|
||||
enabled: false
|
||||
host: localhost
|
||||
port: 5000
|
||||
ring-buffer-size: 512
|
||||
# ===================================================================
|
||||
# Application specific properties
|
||||
# Add your own application properties here, see the ApplicationProperties class
|
||||
# to have type-safe configuration, like in the JHipsterProperties above
|
||||
#
|
||||
# More documentation is available at:
|
||||
# https://www.jhipster.tech/common-application-properties/
|
||||
# ===================================================================
|
||||
|
||||
# application:
|
||||
@@ -0,0 +1,101 @@
|
||||
# ===================================================================
|
||||
# Spring Boot configuration for the "prod" profile.
|
||||
#
|
||||
# This configuration overrides the application.yml file.
|
||||
#
|
||||
# More information on profiles: https://www.jhipster.tech/profiles/
|
||||
# More information on configuration properties: https://www.jhipster.tech/common-application-properties/
|
||||
# ===================================================================
|
||||
|
||||
# ===================================================================
|
||||
# Standard Spring Boot properties.
|
||||
# Full reference is available at:
|
||||
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
|
||||
# ===================================================================
|
||||
|
||||
logging:
|
||||
level:
|
||||
ROOT: INFO
|
||||
tech.jhipster: INFO
|
||||
com.sasiedzi.event: INFO
|
||||
|
||||
management:
|
||||
prometheus:
|
||||
metrics:
|
||||
export:
|
||||
enabled: false
|
||||
|
||||
spring:
|
||||
devtools:
|
||||
restart:
|
||||
enabled: false
|
||||
livereload:
|
||||
enabled: false
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
url: jdbc:postgresql://localhost:5432/sasiedzi
|
||||
username: sasiedzi
|
||||
password: password
|
||||
hikari:
|
||||
poolName: Hikari
|
||||
auto-commit: false
|
||||
# Replace by 'prod, faker' to add the faker context and have sample data loaded in production
|
||||
liquibase:
|
||||
contexts: prod
|
||||
thymeleaf:
|
||||
cache: true
|
||||
|
||||
# ===================================================================
|
||||
# To enable TLS in production, generate a certificate using:
|
||||
# keytool -genkey -alias sasiedzi -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650
|
||||
#
|
||||
# You can also use Let's Encrypt:
|
||||
# See details in topic "Create a Java Keystore (.JKS) from Let's Encrypt Certificates" on https://maximilian-boehm.com/en-gb/blog
|
||||
#
|
||||
# Then, modify the server.ssl properties so your "server" configuration looks like:
|
||||
#
|
||||
# server:
|
||||
# port: 443
|
||||
# ssl:
|
||||
# key-store: classpath:config/tls/keystore.p12
|
||||
# key-store-password: password
|
||||
# key-store-type: PKCS12
|
||||
# key-alias: selfsigned
|
||||
# # The ciphers suite enforce the security by deactivating some old and deprecated SSL cipher, this list was tested against SSL Labs (https://www.ssllabs.com/ssltest/)
|
||||
# ciphers: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 ,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,TLS_RSA_WITH_CAMELLIA_128_CBC_SHA
|
||||
# ===================================================================
|
||||
server:
|
||||
port: 8080
|
||||
shutdown: graceful # see https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-graceful-shutdown
|
||||
compression:
|
||||
enabled: true
|
||||
mime-types: text/html,text/xml,text/plain,text/css,application/javascript,application/json,image/svg+xml
|
||||
min-response-size: 1024
|
||||
|
||||
# ===================================================================
|
||||
# JHipster specific properties
|
||||
#
|
||||
# Full reference is available at: https://www.jhipster.tech/common-application-properties/
|
||||
# ===================================================================
|
||||
|
||||
jhipster:
|
||||
http:
|
||||
cache: # Used by the CachingHttpHeadersFilter
|
||||
timeToLiveInDays: 1461
|
||||
logging:
|
||||
use-json-format: false # By default, logs are not in Json format
|
||||
logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration
|
||||
enabled: false
|
||||
host: localhost
|
||||
port: 5000
|
||||
ring-buffer-size: 512
|
||||
# ===================================================================
|
||||
# Application specific properties
|
||||
# Add your own application properties here, see the ApplicationProperties class
|
||||
# to have type-safe configuration, like in the JHipsterProperties above
|
||||
#
|
||||
# More documentation is available at:
|
||||
# https://www.jhipster.tech/common-application-properties/
|
||||
# ===================================================================
|
||||
|
||||
# application:
|
||||
@@ -0,0 +1,19 @@
|
||||
# ===================================================================
|
||||
# Activate this profile to enable TLS and HTTP/2.
|
||||
#
|
||||
# JHipster has generated a self-signed certificate, which will be used to encrypt traffic.
|
||||
# As your browser will not understand this certificate, you will need to import it.
|
||||
#
|
||||
# Another (easiest) solution with Chrome is to enable the "allow-insecure-localhost" flag
|
||||
# at chrome://flags/#allow-insecure-localhost
|
||||
# ===================================================================
|
||||
server:
|
||||
ssl:
|
||||
key-store: classpath:config/tls/keystore.p12
|
||||
key-store-password: password
|
||||
key-store-type: PKCS12
|
||||
key-alias: selfsigned
|
||||
ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
|
||||
enabled-protocols: TLSv1.2
|
||||
http2:
|
||||
enabled: true
|
||||
@@ -0,0 +1,234 @@
|
||||
# ===================================================================
|
||||
# Spring Boot configuration.
|
||||
#
|
||||
# This configuration will be overridden by the Spring profile you use,
|
||||
# for example application-dev.yml if you use the "dev" profile.
|
||||
#
|
||||
# More information on profiles: https://www.jhipster.tech/profiles/
|
||||
# More information on configuration properties: https://www.jhipster.tech/common-application-properties/
|
||||
# ===================================================================
|
||||
|
||||
# ===================================================================
|
||||
# Standard Spring Boot properties.
|
||||
# Full reference is available at:
|
||||
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
|
||||
# ===================================================================
|
||||
|
||||
---
|
||||
# Conditionally disable springdoc on missing api-docs profile
|
||||
spring:
|
||||
config:
|
||||
activate:
|
||||
on-profile: '!api-docs'
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: false
|
||||
---
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
base-path: /management
|
||||
exposure:
|
||||
include:
|
||||
- configprops
|
||||
- env
|
||||
- health
|
||||
- info
|
||||
- jhimetrics
|
||||
- jhiopenapigroups
|
||||
- logfile
|
||||
- loggers
|
||||
- prometheus
|
||||
- threaddump
|
||||
- liquibase
|
||||
endpoint:
|
||||
health:
|
||||
show-details: when_authorized
|
||||
roles: 'ROLE_ADMIN'
|
||||
probes:
|
||||
enabled: true
|
||||
group:
|
||||
liveness:
|
||||
include: livenessState
|
||||
readiness:
|
||||
include: readinessState,db
|
||||
jhimetrics:
|
||||
enabled: true
|
||||
info:
|
||||
git:
|
||||
mode: full
|
||||
env:
|
||||
enabled: true
|
||||
health:
|
||||
mail:
|
||||
enabled: false # When using the MailService, configure an SMTP server and set this to true
|
||||
prometheus:
|
||||
metrics:
|
||||
export:
|
||||
enabled: true
|
||||
step: 60
|
||||
observations:
|
||||
key-values:
|
||||
application: ${spring.application.name}
|
||||
metrics:
|
||||
enable:
|
||||
http: true
|
||||
jvm: true
|
||||
logback: true
|
||||
process: true
|
||||
system: true
|
||||
distribution:
|
||||
percentiles-histogram:
|
||||
all: true
|
||||
percentiles:
|
||||
all: 0, 0.5, 0.75, 0.95, 0.99, 1.0
|
||||
data:
|
||||
repository:
|
||||
autotime:
|
||||
enabled: true
|
||||
tags:
|
||||
application: ${spring.application.name}
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: sasiedzi
|
||||
docker:
|
||||
compose:
|
||||
enabled: true
|
||||
lifecycle-management: start-only
|
||||
file: src/main/docker/services.yml
|
||||
profiles:
|
||||
# The commented value for `active` can be replaced with valid Spring profiles to load.
|
||||
# Otherwise, it will be filled in by maven when building the JAR file
|
||||
# Either way, it can be overridden by `--spring.profiles.active` value passed in the commandline or `-Dspring.profiles.active` set in `JAVA_OPTS`
|
||||
active: '@spring.profiles.active@'
|
||||
group:
|
||||
dev:
|
||||
- dev
|
||||
- api-docs
|
||||
# Uncomment to activate TLS for the dev profile
|
||||
#- tls
|
||||
jmx:
|
||||
enabled: false
|
||||
data:
|
||||
jpa:
|
||||
repositories:
|
||||
bootstrap-mode: deferred
|
||||
jpa:
|
||||
open-in-view: false
|
||||
properties:
|
||||
hibernate.jdbc.time_zone: UTC
|
||||
hibernate.timezone.default_storage: NORMALIZE
|
||||
hibernate.type.preferred_instant_jdbc_type: TIMESTAMP
|
||||
hibernate.id.new_generator_mappings: true
|
||||
hibernate.connection.provider_disables_autocommit: true
|
||||
hibernate.cache.use_second_level_cache: false
|
||||
hibernate.cache.use_query_cache: false
|
||||
hibernate.generate_statistics: false
|
||||
# modify batch size as necessary
|
||||
hibernate.jdbc.batch_size: 25
|
||||
hibernate.order_inserts: true
|
||||
hibernate.order_updates: true
|
||||
hibernate.query.fail_on_pagination_over_collection_fetch: true
|
||||
hibernate.query.in_clause_parameter_padding: true
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
naming:
|
||||
physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
|
||||
implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
|
||||
messages:
|
||||
basename: i18n/messages
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
mvc:
|
||||
problemdetails:
|
||||
enabled: true
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
provider:
|
||||
oidc:
|
||||
issuer-uri: http://localhost:9080/realms/jhipster
|
||||
registration:
|
||||
oidc:
|
||||
client-id: web_app
|
||||
client-secret: web_app
|
||||
scope: openid, profile, email, offline_access # last one for refresh tokens
|
||||
task:
|
||||
execution:
|
||||
thread-name-prefix: sasiedzi-task-
|
||||
pool:
|
||||
core-size: 2
|
||||
max-size: 50
|
||||
queue-capacity: 10000
|
||||
scheduling:
|
||||
thread-name-prefix: sasiedzi-scheduling-
|
||||
pool:
|
||||
size: 2
|
||||
thymeleaf:
|
||||
mode: HTML
|
||||
output:
|
||||
ansi:
|
||||
console-available: true
|
||||
|
||||
server:
|
||||
servlet:
|
||||
session:
|
||||
cookie:
|
||||
http-only: true
|
||||
|
||||
springdoc:
|
||||
show-actuator: true
|
||||
|
||||
# Properties to be exposed on the /info management endpoint
|
||||
info:
|
||||
# Comma separated list of profiles that will trigger the ribbon to show
|
||||
display-ribbon-on-profiles: 'dev'
|
||||
|
||||
# ===================================================================
|
||||
# JHipster specific properties
|
||||
#
|
||||
# Full reference is available at: https://www.jhipster.tech/common-application-properties/
|
||||
# ===================================================================
|
||||
|
||||
jhipster:
|
||||
clientApp:
|
||||
name: 'sasiedziApp'
|
||||
# By default CORS is disabled. Uncomment to enable.
|
||||
# cors:
|
||||
# allowed-origins: "http://localhost:8100,http://localhost:9000"
|
||||
# allowed-methods: "*"
|
||||
# allowed-headers: "*"
|
||||
# exposed-headers: "Authorization,Link,X-Total-Count,X-${jhipster.clientApp.name}-alert,X-${jhipster.clientApp.name}-error,X-${jhipster.clientApp.name}-params"
|
||||
# allow-credentials: true
|
||||
# max-age: 1800
|
||||
mail:
|
||||
from: sasiedzi@localhost
|
||||
api-docs:
|
||||
default-include-pattern: /api/**
|
||||
management-include-pattern: /management/**
|
||||
title: Sasiedzi API
|
||||
description: Sasiedzi API documentation
|
||||
version: 0.0.1
|
||||
terms-of-service-url:
|
||||
contact-name:
|
||||
contact-url:
|
||||
contact-email:
|
||||
license: unlicensed
|
||||
license-url:
|
||||
security:
|
||||
content-security-policy: "default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:"
|
||||
oauth2:
|
||||
audience:
|
||||
- account
|
||||
- api://default
|
||||
# ===================================================================
|
||||
# Application specific properties
|
||||
# Add your own application properties here, see the ApplicationProperties class
|
||||
# to have type-safe configuration, like in the JHipsterProperties above
|
||||
#
|
||||
# More documentation is available at:
|
||||
# https://www.jhipster.tech/common-application-properties/
|
||||
# ===================================================================
|
||||
|
||||
# application:
|
||||
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
|
||||
|
||||
<changeSet id="00000000000000" author="jhipster">
|
||||
<createSequence sequenceName="sequence_generator" startValue="1050" incrementBy="50"/>
|
||||
</changeSet>
|
||||
|
||||
<!--
|
||||
JHipster core tables.
|
||||
The initial schema has the '00000000000001' id, so that it is over-written if we re-generate it.
|
||||
-->
|
||||
<changeSet id="00000000000001" author="jhipster">
|
||||
<createTable tableName="jhi_user">
|
||||
<column name="id" type="varchar(100)">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="login" type="varchar(50)">
|
||||
<constraints unique="true" nullable="false" uniqueConstraintName="ux_user_login"/>
|
||||
</column>
|
||||
<column name="first_name" type="varchar(50)"/>
|
||||
<column name="last_name" type="varchar(50)"/>
|
||||
<column name="email" type="varchar(191)">
|
||||
<constraints unique="true" nullable="true" uniqueConstraintName="ux_user_email"/>
|
||||
</column>
|
||||
<column name="image_url" type="varchar(256)"/>
|
||||
<column name="activated" type="boolean" valueBoolean="false">
|
||||
<constraints nullable="false" />
|
||||
</column>
|
||||
<column name="lang_key" type="varchar(10)"/>
|
||||
<column name="created_by" type="varchar(50)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_date" type="timestamp"/>
|
||||
<column name="last_modified_by" type="varchar(50)"/>
|
||||
<column name="last_modified_date" type="timestamp"/>
|
||||
</createTable>
|
||||
|
||||
<createTable tableName="jhi_authority">
|
||||
<column name="name" type="varchar(50)">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<createTable tableName="jhi_user_authority">
|
||||
<column name="user_id" type="varchar(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="authority_name" type="varchar(50)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey columnNames="user_id, authority_name" tableName="jhi_user_authority"/>
|
||||
|
||||
<addForeignKeyConstraint baseColumnNames="authority_name"
|
||||
baseTableName="jhi_user_authority"
|
||||
constraintName="fk_authority_name"
|
||||
referencedColumnNames="name"
|
||||
referencedTableName="jhi_authority"/>
|
||||
|
||||
<addForeignKeyConstraint baseColumnNames="user_id"
|
||||
baseTableName="jhi_user_authority"
|
||||
constraintName="fk_user_id"
|
||||
referencedColumnNames="id"
|
||||
referencedTableName="jhi_user"/>
|
||||
|
||||
<loadData
|
||||
file="config/liquibase/data/authority.csv"
|
||||
separator=";"
|
||||
tableName="jhi_authority"
|
||||
usePreparedStatements="true">
|
||||
<column name="name" type="string"/>
|
||||
</loadData>
|
||||
<dropDefaultValue tableName="jhi_user" columnName="created_date" columnDataType="${datetimeType}"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="jhipster" id="00000000000002" context="test">
|
||||
<createTable tableName="jhi_date_time_wrapper">
|
||||
<column name="id" type="BIGINT">
|
||||
<constraints primaryKey="true" primaryKeyName="jhi_date_time_wrapperPK"/>
|
||||
</column>
|
||||
<column name="instant" type="timestamp"/>
|
||||
<column name="local_date_time" type="timestamp"/>
|
||||
<column name="offset_date_time" type="timestamp"/>
|
||||
<column name="zoned_date_time" type="timestamp"/>
|
||||
<column name="local_time" type="time"/>
|
||||
<column name="offset_time" type="time"/>
|
||||
<column name="local_date" type="date"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,3 @@
|
||||
name
|
||||
ROLE_ADMIN
|
||||
ROLE_USER
|
||||
|
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
<property name="now" value="current_timestamp" dbms="postgresql"/>
|
||||
<property name="floatType" value="float4" dbms="postgresql"/>
|
||||
<property name="clobType" value="clob" dbms="postgresql"/>
|
||||
<property name="blobType" value="blob" dbms="postgresql"/>
|
||||
<property name="uuidType" value="uuid" dbms="postgresql"/>
|
||||
<property name="datetimeType" value="datetime" dbms="postgresql"/>
|
||||
|
||||
<include file="config/liquibase/changelog/00000000000000_initial_schema.xml" relativeToChangelogFile="false"/>
|
||||
<!-- jhipster-needle-liquibase-add-changelog - JHipster will add liquibase changelogs here -->
|
||||
<!-- jhipster-needle-liquibase-add-constraints-changelog - JHipster will add liquibase constraints changelogs here -->
|
||||
<!-- jhipster-needle-liquibase-add-incremental-changelog - JHipster will add incremental liquibase changelogs here -->
|
||||
</databaseChangeLog>
|
||||
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
# Error page
|
||||
error.title=Your request cannot be processed
|
||||
error.subtitle=Sorry, an error has occurred.
|
||||
error.status=Status:
|
||||
error.message=Message:
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE configuration>
|
||||
|
||||
<configuration scan="true">
|
||||
<!-- Patterns based on https://github.com/spring-projects/spring-boot/blob/v3.0.0/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml -->
|
||||
<conversionRule conversionWord="crlf" converterClass="com.sasiedzi.event.config.CRLFLogConverter" />
|
||||
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %crlf(%m){red} %n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
|
||||
<!-- The FILE and ASYNC appenders are here as examples for a production configuration -->
|
||||
<!--
|
||||
<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %crlf(%m) %n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
|
||||
-->
|
||||
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
|
||||
|
||||
<!-- The FILE and ASYNC appenders are here as examples for a production configuration -->
|
||||
<!--
|
||||
<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
|
||||
|
||||
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<queueSize>512</queueSize>
|
||||
<appender-ref ref="FILE"/>
|
||||
</appender>
|
||||
|
||||
<root level="${logging.level.root}">
|
||||
<appender-ref ref="ASYNC"/>
|
||||
</root>
|
||||
-->
|
||||
|
||||
<logger name="com.sasiedzi.event" level="INFO"/>
|
||||
|
||||
<logger name="angus.activation" level="WARN"/>
|
||||
<logger name="jakarta.activation" level="WARN"/>
|
||||
<logger name="jakarta.mail" level="WARN"/>
|
||||
<logger name="jakarta.management.remote" level="WARN"/>
|
||||
<logger name="jakarta.xml.bind" level="WARN"/>
|
||||
<logger name="jdk.event.security" level="INFO"/>
|
||||
<logger name="com.ryantenney" level="WARN"/>
|
||||
<logger name="com.sun" level="WARN"/>
|
||||
<logger name="com.zaxxer" level="WARN"/>
|
||||
<logger name="io.undertow" level="WARN"/>
|
||||
<logger name="io.undertow.websockets.jsr" level="ERROR"/>
|
||||
<logger name="org.apache" level="WARN"/>
|
||||
<logger name="org.apache.catalina.startup.DigesterFactory" level="OFF"/>
|
||||
<logger name="org.bson" level="WARN"/>
|
||||
<logger name="org.hibernate.validator" level="WARN"/>
|
||||
<logger name="org.hibernate" level="WARN"/>
|
||||
<logger name="org.hibernate.ejb.HibernatePersistence" level="OFF"/>
|
||||
<logger name="org.postgresql" level="WARN"/>
|
||||
<logger name="org.springframework" level="WARN"/>
|
||||
<logger name="org.springframework.web" level="WARN"/>
|
||||
<logger name="org.springframework.security" level="WARN"/>
|
||||
<logger name="org.springframework.boot.autoconfigure.logging" level="INFO"/>
|
||||
<logger name="org.springframework.cache" level="WARN"/>
|
||||
<logger name="org.thymeleaf" level="WARN"/>
|
||||
<logger name="org.xnio" level="WARN"/>
|
||||
<logger name="io.swagger.v3" level="INFO"/>
|
||||
<logger name="sun.rmi" level="WARN"/>
|
||||
<logger name="sun.rmi.transport" level="WARN"/>
|
||||
<!-- See https://github.com/jhipster/generator-jhipster/issues/13835 -->
|
||||
<logger name="Validator" level="INFO"/>
|
||||
<!-- See https://github.com/jhipster/generator-jhipster/issues/14444 -->
|
||||
<logger name="_org.springframework.web.servlet.HandlerMapping.Mappings" level="INFO"/>
|
||||
<logger name="org.springframework.boot.docker" level="WARN"/>
|
||||
<logger name="liquibase" level="WARN"/>
|
||||
<logger name="LiquibaseSchemaResolver" level="INFO"/>
|
||||
<!-- jhipster-needle-logback-add-log - JHipster will add a new log with level -->
|
||||
|
||||
<springProperty name="log.level" source="logging.level.root" defaultValue="INFO" />
|
||||
<root level="${log.level}">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
|
||||
<!-- Prevent logback from outputting its own status -->
|
||||
<statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
|
||||
<!-- Reset the JUL logging level to avoid conflicts with Logback -->
|
||||
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
|
||||
<resetJUL>true</resetJUL>
|
||||
</contextListener>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!doctype html>
|
||||
<html
|
||||
xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.thymeleaf.org"
|
||||
th:lang="${#locale.language}"
|
||||
lang="en"
|
||||
>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<link rel="icon" href="${baseUrl}/favicon.ico" />
|
||||
<title>Your request cannot be processed</title>
|
||||
<style>
|
||||
::-moz-selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #b3d4fc;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
html {
|
||||
padding: 30px 10px;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
color: #737373;
|
||||
background: #3e8acc;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
html,
|
||||
input {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 1000px;
|
||||
_width: 500px;
|
||||
padding: 30px 20px 50px;
|
||||
border: 1px solid #b3b3b3;
|
||||
border-radius: 4px;
|
||||
margin: 0 auto;
|
||||
box-shadow:
|
||||
0 1px 10px #a7a7a7,
|
||||
inset 0 1px 0 #fff;
|
||||
background: #fcfcfc;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 10px;
|
||||
font-size: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 span {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 1.5em 0 0.5em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0 0 0 40px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
_width: 380px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 th:text="#{error.title}">Your request cannot be processed <span>:(</span></h1>
|
||||
|
||||
<p th:text="#{error.subtitle}">Sorry, an error has occurred.</p>
|
||||
|
||||
<span th:text="#{error.status}">Status:</span> <span th:text="${error}"></span> (<span th:text="${error}"></span>)<br />
|
||||
<span th:if="${!#strings.isEmpty(message)}">
|
||||
<span th:text="#{error.message}">Message:</span> <span th:text="${message}"></span><br />
|
||||
</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,58 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Page Not Found</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="favicon.ico" />
|
||||
<style>
|
||||
* {
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
color: #888;
|
||||
display: table;
|
||||
font-family: sans-serif;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
margin: 2em auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #555;
|
||||
font-size: 2em;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 auto;
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 280px) {
|
||||
body,
|
||||
p {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 0 0 0.3em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Page Not Found</h1>
|
||||
<p>Sorry, but the page you were trying to view does not exist.</p>
|
||||
</body>
|
||||
</html>
|
||||
<!-- IE needs 512+ bytes: http://blogs.msdn.com/b/ieinternals/archive/2010/08/19/http-error-pages-in-internet-explorer.aspx -->
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app
|
||||
xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
|
||||
<mime-mapping>
|
||||
<extension>html</extension>
|
||||
<mime-type>text/html;charset=utf-8</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
</web-app>
|
||||
@@ -0,0 +1,109 @@
|
||||
import axios from 'axios';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import AccountService from './account.service';
|
||||
import { type AccountStore, useStore } from '@/store';
|
||||
|
||||
const resetStore = (store: AccountStore) => {
|
||||
store.$reset();
|
||||
};
|
||||
|
||||
const axiosStub = {
|
||||
get: sinon.stub(axios, 'get'),
|
||||
post: sinon.stub(axios, 'post'),
|
||||
};
|
||||
|
||||
createTestingPinia({ stubActions: false });
|
||||
const store = useStore();
|
||||
|
||||
describe('Account Service test suite', () => {
|
||||
let accountService: AccountService;
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
|
||||
axiosStub.get.reset();
|
||||
resetStore(store);
|
||||
});
|
||||
|
||||
it('should init service and do not retrieve account', async () => {
|
||||
axiosStub.get.resolves({});
|
||||
axiosStub.get
|
||||
.withArgs('management/info')
|
||||
.resolves({ status: 200, data: { 'display-ribbon-on-profiles': 'dev', activeProfiles: ['dev', 'test'] } });
|
||||
|
||||
accountService = new AccountService(store);
|
||||
await accountService.update();
|
||||
|
||||
expect(store.logon).toBe(null);
|
||||
expect(accountService.authenticated).toBe(false);
|
||||
expect(store.account).toBe(null);
|
||||
expect(axiosStub.get.calledWith('management/info')).toBeTruthy();
|
||||
expect(store.activeProfiles[0]).toBe('dev');
|
||||
expect(store.activeProfiles[1]).toBe('test');
|
||||
expect(store.ribbonOnProfiles).toBe('dev');
|
||||
});
|
||||
|
||||
it('should init service and retrieve profiles if already logged in before but no account found', async () => {
|
||||
axiosStub.get.resolves({});
|
||||
accountService = new AccountService(store);
|
||||
await accountService.update();
|
||||
|
||||
expect(store.logon).toBe(null);
|
||||
expect(accountService.authenticated).toBe(false);
|
||||
expect(store.account).toBe(null);
|
||||
expect(axiosStub.get.calledWith('management/info')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should init service and retrieve profiles if already logged in before but exception occurred and should be logged out', async () => {
|
||||
axiosStub.get.resolves({});
|
||||
axiosStub.get.withArgs('api/account').rejects();
|
||||
accountService = new AccountService(store);
|
||||
await accountService.update();
|
||||
|
||||
expect(accountService.authenticated).toBe(false);
|
||||
expect(store.account).toBe(null);
|
||||
expect(axiosStub.get.calledWith('management/info')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should init service and check for authority after retrieving account but getAccount failed', async () => {
|
||||
axiosStub.get.rejects();
|
||||
accountService = new AccountService(store);
|
||||
await accountService.update();
|
||||
|
||||
return accountService.hasAnyAuthorityAndCheckAuth('USER').then((value: boolean) => {
|
||||
expect(value).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should init service and check for authority after retrieving account', async () => {
|
||||
axiosStub.get.resolves({ status: 200, data: { authorities: ['USER'], langKey: 'en', login: 'ADMIN' } });
|
||||
accountService = new AccountService(store);
|
||||
await accountService.update();
|
||||
|
||||
return accountService.hasAnyAuthorityAndCheckAuth('USER').then((value: boolean) => {
|
||||
expect(value).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should init service as not authentified and not return any authorities admin and not retrieve account', async () => {
|
||||
axiosStub.get.rejects();
|
||||
accountService = new AccountService(store);
|
||||
await accountService.update();
|
||||
|
||||
return accountService.hasAnyAuthorityAndCheckAuth('ADMIN').then((value: boolean) => {
|
||||
expect(value).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should init service as not authentified and return authority user', async () => {
|
||||
axiosStub.get.rejects();
|
||||
accountService = new AccountService(store);
|
||||
await accountService.update();
|
||||
|
||||
return accountService.hasAnyAuthorityAndCheckAuth('USER').then((value: boolean) => {
|
||||
expect(value).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { type AccountStore } from '@/store';
|
||||
|
||||
export default class AccountService {
|
||||
constructor(private store: AccountStore) {}
|
||||
|
||||
public async update(): Promise<void> {
|
||||
if (!this.store.profilesLoaded) {
|
||||
await this.retrieveProfiles();
|
||||
this.store.setProfilesLoaded();
|
||||
}
|
||||
await this.loadAccount();
|
||||
}
|
||||
|
||||
public async retrieveProfiles(): Promise<boolean> {
|
||||
try {
|
||||
const res = await axios.get<any>('management/info');
|
||||
if (res.data && res.data.activeProfiles) {
|
||||
this.store.setRibbonOnProfiles(res.data['display-ribbon-on-profiles']);
|
||||
this.store.setActiveProfiles(res.data.activeProfiles);
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async retrieveAccount(): Promise<boolean> {
|
||||
try {
|
||||
const response = await axios.get<any>('api/account');
|
||||
if (response.status === 200 && response.data?.login) {
|
||||
const account = response.data;
|
||||
this.store.setAuthentication(account);
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
}
|
||||
|
||||
this.store.logout();
|
||||
return false;
|
||||
}
|
||||
|
||||
public async loadAccount() {
|
||||
if (this.store.logon) {
|
||||
return this.store.logon;
|
||||
}
|
||||
if (this.authenticated && this.userAuthorities) {
|
||||
return;
|
||||
}
|
||||
|
||||
const promise = this.retrieveAccount();
|
||||
this.store.authenticate(promise);
|
||||
promise.then(() => this.store.authenticate(null));
|
||||
await promise;
|
||||
}
|
||||
|
||||
public async hasAnyAuthorityAndCheckAuth(authorities: any): Promise<boolean> {
|
||||
if (typeof authorities === 'string') {
|
||||
authorities = [authorities];
|
||||
}
|
||||
|
||||
return this.checkAuthorities(authorities);
|
||||
}
|
||||
|
||||
public get authenticated(): boolean {
|
||||
return this.store.authenticated;
|
||||
}
|
||||
|
||||
public get userAuthorities(): string[] {
|
||||
return this.store.account?.authorities;
|
||||
}
|
||||
|
||||
private checkAuthorities(authorities: string[]): boolean {
|
||||
if (this.userAuthorities) {
|
||||
for (const authority of authorities) {
|
||||
if (this.userAuthorities.includes(authority)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import axios from 'axios';
|
||||
import sinon from 'sinon';
|
||||
import LoginService from './login.service';
|
||||
|
||||
const axiosStub = {
|
||||
get: sinon.stub(axios, 'get'),
|
||||
post: sinon.stub(axios, 'post'),
|
||||
};
|
||||
|
||||
describe('Login Service test suite', () => {
|
||||
let loginService: LoginService;
|
||||
|
||||
beforeEach(() => {
|
||||
loginService = new LoginService();
|
||||
});
|
||||
|
||||
it('should build url for login', () => {
|
||||
const loc = { href: '', hostname: 'localhost', pathname: '/' };
|
||||
|
||||
loginService.login(loc);
|
||||
|
||||
expect(loc.href).toBe('//localhost/oauth2/authorization/oidc');
|
||||
});
|
||||
|
||||
it('should build url for login with loc.pathname equals to /accessdenied', () => {
|
||||
const loc = { href: '', hostname: 'localhost', pathname: '/accessdenied' };
|
||||
|
||||
loginService.login(loc);
|
||||
|
||||
expect(loc.href).toBe('//localhost/oauth2/authorization/oidc');
|
||||
});
|
||||
|
||||
it('should build url for login with loc.pathname equals to /forbidden', () => {
|
||||
const loc = { href: '', hostname: 'localhost', pathname: '/forbidden' };
|
||||
|
||||
loginService.login(loc);
|
||||
|
||||
expect(loc.href).toBe('//localhost/oauth2/authorization/oidc');
|
||||
});
|
||||
|
||||
it('should build url for login with loc.pathname equals to /accessdenied', () => {
|
||||
const loc = { href: '', hostname: 'localhost', pathname: '/accessdenied' };
|
||||
|
||||
loginService.login(loc);
|
||||
|
||||
expect(loc.href).toBe('//localhost/oauth2/authorization/oidc');
|
||||
});
|
||||
|
||||
it('should build url for login with loc.pathname equals to /forbidden', () => {
|
||||
const loc = { href: '', hostname: 'localhost', pathname: '/forbidden' };
|
||||
|
||||
loginService.login(loc);
|
||||
|
||||
expect(loc.href).toBe('//localhost/oauth2/authorization/oidc');
|
||||
});
|
||||
|
||||
it('should build url for login behind client proxy', () => {
|
||||
const loc = { href: '', port: '8080', hostname: 'localhost', pathname: '/' };
|
||||
|
||||
loginService.login(loc);
|
||||
|
||||
expect(loc.href).toBe('//localhost:8080/oauth2/authorization/oidc');
|
||||
});
|
||||
|
||||
it('should call global logout when asked to', () => {
|
||||
loginService.logout();
|
||||
|
||||
expect(axiosStub.post.calledWith('api/logout')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import axios, { type AxiosPromise } from 'axios';
|
||||
|
||||
export default class LoginService {
|
||||
public login(loc: { href: string; hostname: string; pathname: string; port?: string } = window.location) {
|
||||
const port = loc.port ? `:${loc.port}` : '';
|
||||
let contextPath = loc.pathname;
|
||||
if (contextPath.endsWith('accessdenied')) {
|
||||
contextPath = contextPath.substring(0, contextPath.indexOf('accessdenied'));
|
||||
}
|
||||
if (contextPath.endsWith('forbidden')) {
|
||||
contextPath = contextPath.substring(0, contextPath.indexOf('forbidden'));
|
||||
}
|
||||
if (!contextPath.endsWith('/')) {
|
||||
contextPath = `${contextPath}/`;
|
||||
}
|
||||
// If you have configured multiple OIDC providers, then, you can update this URL to /login.
|
||||
// It will show a Spring Security generated login page with links to configured OIDC providers.
|
||||
loc.href = `//${loc.hostname}${port}${contextPath}oauth2/authorization/oidc`;
|
||||
}
|
||||
|
||||
public logout(): AxiosPromise<any> {
|
||||
return axios.post('api/logout');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import axios from 'axios';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Configuration from './configuration.vue';
|
||||
|
||||
type ConfigurationComponentType = InstanceType<typeof Configuration>;
|
||||
|
||||
const axiosStub = {
|
||||
get: sinon.stub(axios, 'get'),
|
||||
};
|
||||
|
||||
describe('Configuration Component', () => {
|
||||
let configuration: ConfigurationComponentType;
|
||||
|
||||
beforeEach(() => {
|
||||
axiosStub.get.reset();
|
||||
axiosStub.get.resolves({
|
||||
data: { contexts: [{ beans: [{ prefix: 'A' }, { prefix: 'B' }] }], propertySources: [{ properties: { key1: { value: 'value' } } }] },
|
||||
});
|
||||
const wrapper = shallowMount(Configuration);
|
||||
configuration = wrapper.vm;
|
||||
});
|
||||
|
||||
describe('OnRouteEnter', () => {
|
||||
it('should set all default values correctly', () => {
|
||||
expect(configuration.configKeys).toEqual([]);
|
||||
expect(configuration.filtered).toBe('');
|
||||
expect(configuration.orderProp).toBe('prefix');
|
||||
expect(configuration.reverse).toBe(false);
|
||||
});
|
||||
it('Should call load all on init', async () => {
|
||||
// WHEN
|
||||
configuration.init();
|
||||
await configuration.$nextTick();
|
||||
|
||||
// THEN
|
||||
expect(axiosStub.get.calledWith('management/env')).toBeTruthy();
|
||||
expect(axiosStub.get.calledWith('management/configprops')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('keys method', () => {
|
||||
it('should return the keys of an Object', () => {
|
||||
// GIVEN
|
||||
const data = {
|
||||
key1: 'test',
|
||||
key2: 'test2',
|
||||
};
|
||||
|
||||
// THEN
|
||||
expect(configuration.keys(data)).toEqual(['key1', 'key2']);
|
||||
expect(configuration.keys(undefined)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeOrder function', () => {
|
||||
it('should change order', () => {
|
||||
// GIVEN
|
||||
const rev = configuration.reverse;
|
||||
|
||||
// WHEN
|
||||
configuration.changeOrder('prefix');
|
||||
|
||||
// THEN
|
||||
expect(configuration.orderProp).toBe('prefix');
|
||||
expect(configuration.reverse).toBe(!rev);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import { type Ref, computed, defineComponent, inject, ref } from 'vue';
|
||||
|
||||
import ConfigurationService from './configuration.service';
|
||||
import { orderAndFilterBy } from '@/shared/computables';
|
||||
|
||||
export default defineComponent({
|
||||
compatConfig: { MODE: 3 },
|
||||
name: 'JhiConfiguration',
|
||||
setup() {
|
||||
const configurationService = inject('configurationService', () => new ConfigurationService(), true);
|
||||
|
||||
const orderProp = ref('prefix');
|
||||
const reverse = ref(false);
|
||||
const allConfiguration: Ref<any> = ref({});
|
||||
const configuration: Ref<any[]> = ref([]);
|
||||
const configKeys: Ref<any[]> = ref([]);
|
||||
const filtered = ref('');
|
||||
|
||||
const filteredConfiguration = computed(() =>
|
||||
orderAndFilterBy(configuration.value, {
|
||||
filterByTerm: filtered.value,
|
||||
orderByProp: orderProp.value,
|
||||
reverse: reverse.value,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
configurationService,
|
||||
orderProp,
|
||||
reverse,
|
||||
allConfiguration,
|
||||
configuration,
|
||||
configKeys,
|
||||
filtered,
|
||||
filteredConfiguration,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
init(): void {
|
||||
this.configurationService.loadConfiguration().then(res => {
|
||||
this.configuration = res;
|
||||
|
||||
for (const config of this.configuration) {
|
||||
if (config.properties !== undefined) {
|
||||
this.configKeys.push(Object.keys(config.properties));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.configurationService.loadEnvConfiguration().then(res => {
|
||||
this.allConfiguration = res;
|
||||
});
|
||||
},
|
||||
changeOrder(prop: string): void {
|
||||
this.orderProp = prop;
|
||||
this.reverse = !this.reverse;
|
||||
},
|
||||
keys(dict: any): string[] {
|
||||
return dict === undefined ? [] : Object.keys(dict);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export default class ConfigurationService {
|
||||
public loadConfiguration(): Promise<any> {
|
||||
return new Promise(resolve => {
|
||||
axios.get('management/configprops').then(res => {
|
||||
const properties = [];
|
||||
const propertiesObject = this.getConfigPropertiesObjects(res.data);
|
||||
for (const key in propertiesObject) {
|
||||
if (Object.hasOwn(propertiesObject, key)) {
|
||||
properties.push(propertiesObject[key]);
|
||||
}
|
||||
}
|
||||
|
||||
properties.sort((propertyA, propertyB) => {
|
||||
const comparePrefix = propertyA.prefix < propertyB.prefix ? -1 : 1;
|
||||
return propertyA.prefix === propertyB.prefix ? 0 : comparePrefix;
|
||||
});
|
||||
resolve(properties);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public loadEnvConfiguration(): Promise<any> {
|
||||
return new Promise(resolve => {
|
||||
axios.get<any>('management/env').then(res => {
|
||||
const properties = {};
|
||||
const propertySources = res.data.propertySources;
|
||||
|
||||
for (const propertyObject of propertySources) {
|
||||
const name = propertyObject.name;
|
||||
const detailProperties = propertyObject.properties;
|
||||
const vals = [];
|
||||
for (const keyDetail in detailProperties) {
|
||||
if (Object.hasOwn(detailProperties, keyDetail)) {
|
||||
vals.push({ key: keyDetail, val: detailProperties[keyDetail].value });
|
||||
}
|
||||
}
|
||||
properties[name] = vals;
|
||||
}
|
||||
resolve(properties);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getConfigPropertiesObjects(res): any {
|
||||
// This code is for Spring Boot 2
|
||||
if (res.contexts !== undefined) {
|
||||
for (const key in res.contexts) {
|
||||
// If the key is not bootstrap, it will be the ApplicationContext Id
|
||||
// For default app, it is baseName
|
||||
// For microservice, it is baseName-1
|
||||
if (!key.startsWith('bootstrap')) {
|
||||
return res.contexts[key].beans;
|
||||
}
|
||||
}
|
||||
}
|
||||
// by default, use the default ApplicationContext Id
|
||||
return res.contexts.sasiedzi.beans;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 id="configuration-page-heading" data-cy="configurationPageHeading">Configuration</h2>
|
||||
|
||||
<div v-if="allConfiguration && configuration">
|
||||
<span>Filter (by prefix)</span> <input type="text" v-model="filtered" class="form-control" />
|
||||
<h3>Spring configuration</h3>
|
||||
<table class="table table-striped table-bordered table-responsive d-table" aria-describedby="Configuration">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-40" @click="changeOrder('prefix')" scope="col">
|
||||
<span>Prefix</span>
|
||||
</th>
|
||||
<th class="w-60" @click="changeOrder('properties')" scope="col">
|
||||
<span>Properties</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="entry in filteredConfiguration" :key="entry.prefix">
|
||||
<td>
|
||||
<span>{{ entry.prefix }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="row" v-for="key in keys(entry.properties)" :key="key">
|
||||
<div class="col-md-4">{{ key }}</div>
|
||||
<div class="col-md-8">
|
||||
<span class="float-right badge-secondary break">{{ entry.properties[key] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-for="key in keys(allConfiguration)" :key="key">
|
||||
<h4>
|
||||
<span>{{ key }}</span>
|
||||
</h4>
|
||||
<table class="table table-sm table-striped table-bordered table-responsive d-table" aria-describedby="Properties">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-40" scope="col">Property</th>
|
||||
<th class="w-60" scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item of allConfiguration[key]" :key="item.key">
|
||||
<td class="break">{{ item.key }}</td>
|
||||
<td class="break">
|
||||
<span class="float-right badge-secondary break">{{ item.val }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" src="./configuration.component.ts"></script>
|
||||
@@ -0,0 +1,6 @@
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
compatConfig: { MODE: 3 },
|
||||
name: 'JhiDocs',
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<iframe
|
||||
src="/swagger-ui/index.html"
|
||||
width="100%"
|
||||
height="900"
|
||||
seamless
|
||||
target="_top"
|
||||
title="Swagger UI"
|
||||
class="border-0"
|
||||
data-cy="swagger-frame"
|
||||
></iframe>
|
||||
</template>
|
||||
|
||||
<script lang="ts" src="./docs.component.ts"></script>
|
||||
@@ -0,0 +1,89 @@
|
||||
import { vitest } from 'vitest';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
import HealthModal from './health-modal.vue';
|
||||
|
||||
type HealthModalComponentType = InstanceType<typeof HealthModal>;
|
||||
|
||||
const healthService = { getBaseName: vitest.fn(), getSubSystemName: vitest.fn() };
|
||||
|
||||
describe('Health Modal Component', () => {
|
||||
let healthModal: HealthModalComponentType;
|
||||
|
||||
beforeEach(() => {
|
||||
const wrapper = shallowMount(HealthModal, {
|
||||
propsData: {
|
||||
currentHealth: {},
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
'font-awesome-icon': true,
|
||||
},
|
||||
provide: {
|
||||
healthService,
|
||||
},
|
||||
},
|
||||
});
|
||||
healthModal = wrapper.vm;
|
||||
});
|
||||
|
||||
describe('baseName and subSystemName', () => {
|
||||
it('should use healthService', () => {
|
||||
healthModal.baseName('base');
|
||||
|
||||
expect(healthService.getBaseName).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use healthService', () => {
|
||||
healthModal.subSystemName('base');
|
||||
|
||||
expect(healthService.getSubSystemName).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('readableValue should transform data', () => {
|
||||
it('to string when is an object', () => {
|
||||
const result = healthModal.readableValue({ data: 1000 });
|
||||
|
||||
expect(result).toBe('{"data":1000}');
|
||||
});
|
||||
|
||||
it('to string when is a string', () => {
|
||||
const result = healthModal.readableValue('data');
|
||||
|
||||
expect(result).toBe('data');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Health Modal Component for diskSpace', () => {
|
||||
let healthModal: HealthModalComponentType;
|
||||
|
||||
beforeEach(() => {
|
||||
const wrapper = shallowMount(HealthModal, {
|
||||
propsData: {
|
||||
currentHealth: { name: 'diskSpace' },
|
||||
},
|
||||
global: {
|
||||
provide: {
|
||||
healthService,
|
||||
},
|
||||
},
|
||||
});
|
||||
healthModal = wrapper.vm;
|
||||
});
|
||||
|
||||
describe('readableValue should transform data', () => {
|
||||
it('to GB when needed', () => {
|
||||
const result = healthModal.readableValue(2147483648);
|
||||
|
||||
expect(result).toBe('2.00 GB');
|
||||
});
|
||||
|
||||
it('to MB when needed', () => {
|
||||
const result = healthModal.readableValue(214748);
|
||||
|
||||
expect(result).toBe('0.20 MB');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import HealthService from './health.service';
|
||||
|
||||
export default defineComponent({
|
||||
compatConfig: { MODE: 3 },
|
||||
name: 'JhiHealthModal',
|
||||
props: {
|
||||
currentHealth: {},
|
||||
},
|
||||
setup() {
|
||||
const healthService = inject('healthService', () => new HealthService(), true);
|
||||
|
||||
return {
|
||||
healthService,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
baseName(name: string): any {
|
||||
return this.healthService.getBaseName(name);
|
||||
},
|
||||
subSystemName(name: string): any {
|
||||
return this.healthService.getSubSystemName(name);
|
||||
},
|
||||
readableValue(value: any): string {
|
||||
if (this.currentHealth.name === 'diskSpace') {
|
||||
// Should display storage space in an human readable unit
|
||||
const val = value / 1073741824;
|
||||
if (val > 1) {
|
||||
// Value
|
||||
return `${val.toFixed(2)} GB`;
|
||||
}
|
||||
return `${(value / 1048576).toFixed(2)} MB`;
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
return value.toString();
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="modal-body pad">
|
||||
<div v-if="currentHealth && currentHealth.details">
|
||||
<h5>Properties</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" aria-describedby="Health">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left" scope="col">Name</th>
|
||||
<th class="text-left" scope="col">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in currentHealth.details.details" :key="index">
|
||||
<td class="text-left">{{ index }}</td>
|
||||
<td class="text-left">{{ readableValue(item) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="currentHealth && currentHealth.error">
|
||||
<h4>Error</h4>
|
||||
<pre>{{ currentHealth.error }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" src="./health-modal.component.ts"></script>
|
||||
@@ -0,0 +1,92 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import axios from 'axios';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Health from './health.vue';
|
||||
import HealthService from './health.service';
|
||||
|
||||
type HealthComponentType = InstanceType<typeof Health>;
|
||||
|
||||
const axiosStub = {
|
||||
get: sinon.stub(axios, 'get'),
|
||||
};
|
||||
|
||||
describe('Health Component', () => {
|
||||
let health: HealthComponentType;
|
||||
|
||||
beforeEach(() => {
|
||||
axiosStub.get.resolves({});
|
||||
const wrapper = shallowMount(Health, {
|
||||
global: {
|
||||
stubs: {
|
||||
bModal: true,
|
||||
'font-awesome-icon': true,
|
||||
'health-modal': true,
|
||||
},
|
||||
directives: {
|
||||
'b-modal': {},
|
||||
},
|
||||
provide: {
|
||||
healthService: new HealthService(),
|
||||
},
|
||||
},
|
||||
});
|
||||
health = wrapper.vm;
|
||||
});
|
||||
|
||||
describe('baseName and subSystemName', () => {
|
||||
it('should return the basename when it has no sub system', () => {
|
||||
expect(health.baseName('base')).toBe('base');
|
||||
});
|
||||
|
||||
it('should return the basename when it has sub systems', () => {
|
||||
expect(health.baseName('base.subsystem.system')).toBe('base');
|
||||
});
|
||||
|
||||
it('should return the sub system name', () => {
|
||||
expect(health.subSystemName('subsystem')).toBe('');
|
||||
});
|
||||
|
||||
it('should return the subsystem when it has multiple keys', () => {
|
||||
expect(health.subSystemName('subsystem.subsystem.system')).toBe(' - subsystem.system');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBadgeClass', () => {
|
||||
it('should get badge class', () => {
|
||||
const upBadgeClass = health.getBadgeClass('UP');
|
||||
const downBadgeClass = health.getBadgeClass('DOWN');
|
||||
expect(upBadgeClass).toEqual('badge-success');
|
||||
expect(downBadgeClass).toEqual('badge-danger');
|
||||
});
|
||||
});
|
||||
|
||||
describe('refresh', () => {
|
||||
it('should call refresh on init', async () => {
|
||||
// GIVEN
|
||||
axiosStub.get.resolves({});
|
||||
|
||||
// WHEN
|
||||
health.refresh();
|
||||
await health.$nextTick();
|
||||
|
||||
// THEN
|
||||
expect(axiosStub.get.calledWith('management/health')).toBeTruthy();
|
||||
await health.$nextTick();
|
||||
expect(health.updatingHealth).toEqual(false);
|
||||
});
|
||||
it('should handle a 503 on refreshing health data', async () => {
|
||||
// GIVEN
|
||||
axiosStub.get.rejects({});
|
||||
|
||||
// WHEN
|
||||
health.refresh();
|
||||
await health.$nextTick();
|
||||
|
||||
// THEN
|
||||
expect(axiosStub.get.calledWith('management/health')).toBeTruthy();
|
||||
await health.$nextTick();
|
||||
expect(health.updatingHealth).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { type Ref, defineComponent, inject, ref } from 'vue';
|
||||
|
||||
import HealthService from './health.service';
|
||||
import JhiHealthModal from './health-modal.vue';
|
||||
|
||||
export default defineComponent({
|
||||
compatConfig: { MODE: 3 },
|
||||
name: 'JhiHealth',
|
||||
components: {
|
||||
'health-modal': JhiHealthModal,
|
||||
},
|
||||
setup() {
|
||||
const healthService = inject('healthService', () => new HealthService(), true);
|
||||
|
||||
const healthData: Ref<any> = ref(null);
|
||||
const currentHealth: Ref<any> = ref(null);
|
||||
const updatingHealth = ref(false);
|
||||
|
||||
return {
|
||||
healthService,
|
||||
healthData,
|
||||
currentHealth,
|
||||
updatingHealth,
|
||||
};
|
||||
},
|
||||
mounted(): void {
|
||||
this.refresh();
|
||||
},
|
||||
methods: {
|
||||
baseName(name: any): any {
|
||||
return this.healthService.getBaseName(name);
|
||||
},
|
||||
getBadgeClass(statusState: any): string {
|
||||
if (statusState === 'UP') {
|
||||
return 'badge-success';
|
||||
}
|
||||
return 'badge-danger';
|
||||
},
|
||||
refresh(): void {
|
||||
this.updatingHealth = true;
|
||||
this.healthService
|
||||
.checkHealth()
|
||||
.then(res => {
|
||||
this.healthData = this.healthService.transformHealthData(res.data);
|
||||
this.updatingHealth = false;
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.status === 503) {
|
||||
this.healthData = this.healthService.transformHealthData(error.error);
|
||||
}
|
||||
this.updatingHealth = false;
|
||||
});
|
||||
},
|
||||
showHealth(health: any): void {
|
||||
this.currentHealth = health;
|
||||
(<any>this.$refs.healthModal).show();
|
||||
},
|
||||
subSystemName(name: string): string {
|
||||
return this.healthService.getSubSystemName(name);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,244 @@
|
||||
import HealthService from './health.service';
|
||||
|
||||
describe('Health Service', () => {
|
||||
let healthService: HealthService;
|
||||
|
||||
beforeEach(() => {
|
||||
healthService = new HealthService();
|
||||
});
|
||||
|
||||
describe('transformHealthData', () => {
|
||||
it('should flatten empty health data', () => {
|
||||
const data = {};
|
||||
const expected = [];
|
||||
expect(healthService.transformHealthData(data)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should flatten health data with no subsystems', () => {
|
||||
const data = {
|
||||
components: {
|
||||
status: 'UP',
|
||||
db: {
|
||||
status: 'UP',
|
||||
database: 'H2',
|
||||
hello: '1',
|
||||
},
|
||||
mail: {
|
||||
status: 'UP',
|
||||
error: 'mail.a.b.c',
|
||||
},
|
||||
},
|
||||
};
|
||||
const expected = [
|
||||
{
|
||||
name: 'db',
|
||||
status: 'UP',
|
||||
details: {
|
||||
database: 'H2',
|
||||
hello: '1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mail',
|
||||
error: 'mail.a.b.c',
|
||||
status: 'UP',
|
||||
},
|
||||
];
|
||||
expect(healthService.transformHealthData(data)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should flatten health data with subsystems at level 1, main system has no additional information', () => {
|
||||
const data = {
|
||||
components: {
|
||||
status: 'UP',
|
||||
db: {
|
||||
status: 'UP',
|
||||
database: 'H2',
|
||||
hello: '1',
|
||||
},
|
||||
mail: {
|
||||
status: 'UP',
|
||||
error: 'mail.a.b.c',
|
||||
},
|
||||
system: {
|
||||
status: 'DOWN',
|
||||
subsystem1: {
|
||||
status: 'UP',
|
||||
property1: 'system.subsystem1.property1',
|
||||
},
|
||||
subsystem2: {
|
||||
status: 'DOWN',
|
||||
error: 'system.subsystem1.error',
|
||||
property2: 'system.subsystem2.property2',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const expected = [
|
||||
{
|
||||
name: 'db',
|
||||
status: 'UP',
|
||||
details: {
|
||||
database: 'H2',
|
||||
hello: '1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mail',
|
||||
error: 'mail.a.b.c',
|
||||
status: 'UP',
|
||||
},
|
||||
{
|
||||
name: 'system.subsystem1',
|
||||
status: 'UP',
|
||||
details: {
|
||||
property1: 'system.subsystem1.property1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'system.subsystem2',
|
||||
error: 'system.subsystem1.error',
|
||||
status: 'DOWN',
|
||||
details: {
|
||||
property2: 'system.subsystem2.property2',
|
||||
},
|
||||
},
|
||||
];
|
||||
expect(healthService.transformHealthData(data)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should flatten health data with subsystems at level 1, main system has additional information', () => {
|
||||
const data = {
|
||||
components: {
|
||||
status: 'UP',
|
||||
db: {
|
||||
status: 'UP',
|
||||
database: 'H2',
|
||||
hello: '1',
|
||||
},
|
||||
mail: {
|
||||
status: 'UP',
|
||||
error: 'mail.a.b.c',
|
||||
},
|
||||
system: {
|
||||
status: 'DOWN',
|
||||
property1: 'system.property1',
|
||||
subsystem1: {
|
||||
status: 'UP',
|
||||
property1: 'system.subsystem1.property1',
|
||||
},
|
||||
subsystem2: {
|
||||
status: 'DOWN',
|
||||
error: 'system.subsystem1.error',
|
||||
property2: 'system.subsystem2.property2',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const expected = [
|
||||
{
|
||||
name: 'db',
|
||||
status: 'UP',
|
||||
details: {
|
||||
database: 'H2',
|
||||
hello: '1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mail',
|
||||
error: 'mail.a.b.c',
|
||||
status: 'UP',
|
||||
},
|
||||
{
|
||||
name: 'system',
|
||||
status: 'DOWN',
|
||||
details: {
|
||||
property1: 'system.property1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'system.subsystem1',
|
||||
status: 'UP',
|
||||
details: {
|
||||
property1: 'system.subsystem1.property1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'system.subsystem2',
|
||||
error: 'system.subsystem1.error',
|
||||
status: 'DOWN',
|
||||
details: {
|
||||
property2: 'system.subsystem2.property2',
|
||||
},
|
||||
},
|
||||
];
|
||||
expect(healthService.transformHealthData(data)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should flatten health data with subsystems at level 1, main system has additional error', () => {
|
||||
const data = {
|
||||
components: {
|
||||
status: 'UP',
|
||||
db: {
|
||||
status: 'UP',
|
||||
database: 'H2',
|
||||
hello: '1',
|
||||
},
|
||||
mail: {
|
||||
status: 'UP',
|
||||
error: 'mail.a.b.c',
|
||||
},
|
||||
system: {
|
||||
status: 'DOWN',
|
||||
error: 'show me',
|
||||
subsystem1: {
|
||||
status: 'UP',
|
||||
property1: 'system.subsystem1.property1',
|
||||
},
|
||||
subsystem2: {
|
||||
status: 'DOWN',
|
||||
error: 'system.subsystem1.error',
|
||||
property2: 'system.subsystem2.property2',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const expected = [
|
||||
{
|
||||
name: 'db',
|
||||
status: 'UP',
|
||||
details: {
|
||||
database: 'H2',
|
||||
hello: '1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mail',
|
||||
error: 'mail.a.b.c',
|
||||
status: 'UP',
|
||||
},
|
||||
{
|
||||
name: 'system',
|
||||
error: 'show me',
|
||||
status: 'DOWN',
|
||||
},
|
||||
{
|
||||
name: 'system.subsystem1',
|
||||
status: 'UP',
|
||||
details: {
|
||||
property1: 'system.subsystem1.property1',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'system.subsystem2',
|
||||
error: 'system.subsystem1.error',
|
||||
status: 'DOWN',
|
||||
details: {
|
||||
property2: 'system.subsystem2.property2',
|
||||
},
|
||||
},
|
||||
];
|
||||
expect(healthService.transformHealthData(data)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user