Initial version of sasiedzi generated by generator-jhipster@8.7.2
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user