1
0

Initial application generated by JHipster-5.8.2

This commit is contained in:
Michael Hoennig
2019-04-01 13:14:56 +02:00
commit e0b3d2a36d
404 changed files with 49698 additions and 0 deletions

View File

@ -0,0 +1,21 @@
package org.hostsharing.hsadminng;
import org.hostsharing.hsadminng.config.DefaultProfileUtil;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* This is a helper Java class that provides an alternative to creating a 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(HsadminNgApp.class);
}
}

View File

@ -0,0 +1,98 @@
package org.hostsharing.hsadminng;
import org.hostsharing.hsadminng.config.ApplicationProperties;
import org.hostsharing.hsadminng.config.DefaultProfileUtil;
import io.github.jhipster.config.JHipsterConstants;
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 javax.annotation.PostConstruct;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
@SpringBootApplication
@EnableConfigurationProperties({LiquibaseProperties.class, ApplicationProperties.class})
public class HsadminNgApp {
private static final Logger log = LoggerFactory.getLogger(HsadminNgApp.class);
private final Environment env;
public HsadminNgApp(Environment env) {
this.env = env;
}
/**
* Initializes hsadminNg.
* <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(HsadminNgApp.class);
DefaultProfileUtil.addDefaultProfile(app);
Environment env = app.run(args).getEnvironment();
logApplicationStartup(env);
}
private static void logApplicationStartup(Environment env) {
String protocol = "http";
if (env.getProperty("server.ssl.key-store") != null) {
protocol = "https";
}
String serverPort = env.getProperty("server.port");
String contextPath = env.getProperty("server.servlet.context-path");
if (StringUtils.isBlank(contextPath)) {
contextPath = "/";
}
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("\n----------------------------------------------------------\n\t" +
"Application '{}' is running! Access URLs:\n\t" +
"Local: \t\t{}://localhost:{}{}\n\t" +
"External: \t{}://{}:{}{}\n\t" +
"Profile(s): \t{}\n----------------------------------------------------------",
env.getProperty("spring.application.name"),
protocol,
serverPort,
contextPath,
protocol,
hostAddress,
serverPort,
contextPath,
env.getActiveProfiles());
}
}

View File

@ -0,0 +1,98 @@
package org.hostsharing.hsadminng.aop.logging;
import io.github.jhipster.config.JHipsterConstants;
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 java.util.Arrays;
/**
* 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 Logger log = LoggerFactory.getLogger(this.getClass());
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(org.hostsharing.hsadminng.repository..*)"+
" || within(org.hostsharing.hsadminng.service..*)"+
" || within(org.hostsharing.hsadminng.web.rest..*)")
public void applicationPackagePointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
/**
* 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(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) {
log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL", e.getMessage(), e);
} else {
log.error("Exception in {}.{}() with cause = {}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), e.getCause() != null? e.getCause() : "NULL");
}
}
/**
* Advice that logs when a method is entered and exited.
*
* @param joinPoint join point for advice
* @return result
* @throws Throwable throws IllegalArgumentException
*/
@Around("applicationPackagePointcut() && springBeanPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
if (log.isDebugEnabled()) {
log.debug("Enter: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
}
try {
Object result = joinPoint.proceed();
if (log.isDebugEnabled()) {
log.debug("Exit: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), result);
}
return result;
} catch (IllegalArgumentException e) {
log.error("Illegal argument: {} in {}.{}()", Arrays.toString(joinPoint.getArgs()),
joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
throw e;
}
}
}

View File

@ -0,0 +1,14 @@
package org.hostsharing.hsadminng.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Properties specific to Hsadmin Ng.
* <p>
* Properties are configured in the application.yml file.
* See {@link io.github.jhipster.config.JHipsterProperties} for a good example.
*/
@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
public class ApplicationProperties {
}

View File

@ -0,0 +1,59 @@
package org.hostsharing.hsadminng.config;
import io.github.jhipster.async.ExceptionHandlingAsyncTaskExecutor;
import io.github.jhipster.config.JHipsterProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.*;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer, SchedulingConfigurer {
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
private final JHipsterProperties jHipsterProperties;
public AsyncConfiguration(JHipsterProperties jHipsterProperties) {
this.jHipsterProperties = jHipsterProperties;
}
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
log.debug("Creating Async Task Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(jHipsterProperties.getAsync().getCorePoolSize());
executor.setMaxPoolSize(jHipsterProperties.getAsync().getMaxPoolSize());
executor.setQueueCapacity(jHipsterProperties.getAsync().getQueueCapacity());
executor.setThreadNamePrefix("hsadmin-ng-Executor-");
return new ExceptionHandlingAsyncTaskExecutor(executor);
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(scheduledTaskExecutor());
}
@Bean
public Executor scheduledTaskExecutor() {
return Executors.newScheduledThreadPool(jHipsterProperties.getAsync().getCorePoolSize());
}
}

View File

@ -0,0 +1,39 @@
package org.hostsharing.hsadminng.config;
import java.time.Duration;
import org.ehcache.config.builders.*;
import org.ehcache.jsr107.Eh107Configuration;
import io.github.jhipster.config.JHipsterProperties;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.*;
@Configuration
@EnableCaching
public class CacheConfiguration {
private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;
public CacheConfiguration(JHipsterProperties jHipsterProperties) {
JHipsterProperties.Cache.Ehcache ehcache =
jHipsterProperties.getCache().getEhcache();
jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class,
ResourcePoolsBuilder.heap(ehcache.getMaxEntries()))
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds())))
.build());
}
@Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
return cm -> {
cm.createCache(org.hostsharing.hsadminng.repository.UserRepository.USERS_BY_LOGIN_CACHE, jcacheConfiguration);
cm.createCache(org.hostsharing.hsadminng.repository.UserRepository.USERS_BY_EMAIL_CACHE, jcacheConfiguration);
// jhipster-needle-ehcache-add-entry
};
}
}

View File

@ -0,0 +1,28 @@
package org.hostsharing.hsadminng.config;
import io.github.jhipster.config.JHipsterConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.config.java.AbstractCloudConfig;
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Configuration
@Profile(JHipsterConstants.SPRING_PROFILE_CLOUD)
public class CloudDatabaseConfiguration extends AbstractCloudConfig {
private final Logger log = LoggerFactory.getLogger(CloudDatabaseConfiguration.class);
private static final String CLOUD_CONFIGURATION_HIKARI_PREFIX = "spring.datasource.hikari";
@Bean
@ConfigurationProperties(CLOUD_CONFIGURATION_HIKARI_PREFIX)
public DataSource dataSource() {
log.info("Configuring JDBC datasource from a cloud provider");
return connectionFactory().dataSource();
}
}

View File

@ -0,0 +1,17 @@
package org.hostsharing.hsadminng.config;
/**
* Application constants.
*/
public final class Constants {
// Regex for acceptable logins
public static final String LOGIN_REGEX = "^[_.@A-Za-z0-9-]*$";
public static final String SYSTEM_ACCOUNT = "system";
public static final String ANONYMOUS_USER = "anonymoususer";
public static final String DEFAULT_LANGUAGE = "de";
private Constants() {
}
}

View File

@ -0,0 +1,59 @@
package org.hostsharing.hsadminng.config;
import io.github.jhipster.config.JHipsterConstants;
import io.github.jhipster.config.h2.H2ConfigurationHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.sql.SQLException;
@Configuration
@EnableJpaRepositories("org.hostsharing.hsadminng.repository")
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
@EnableTransactionManagement
public class DatabaseConfiguration {
private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class);
private final Environment env;
public DatabaseConfiguration(Environment env) {
this.env = env;
}
/**
* Open the TCP port for the H2 database, so it is available remotely.
*
* @return the H2 database TCP server
* @throws SQLException if the server failed to start
*/
@Bean(initMethod = "start", destroyMethod = "stop")
@Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
public Object h2TCPServer() throws SQLException {
String port = getValidPortForH2();
log.debug("H2 database is available on port {}", port);
return H2ConfigurationHelper.createServer(port);
}
private String getValidPortForH2() {
int port = Integer.parseInt(env.getProperty("server.port"));
if (port < 10000) {
port = 10000 + port;
} else {
if (port < 63536) {
port = port + 2000;
} else {
port = port - 2000;
}
}
return String.valueOf(port);
}
}

View File

@ -0,0 +1,20 @@
package org.hostsharing.hsadminng.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);
}
}

View File

@ -0,0 +1,51 @@
package org.hostsharing.hsadminng.config;
import io.github.jhipster.config.JHipsterConstants;
import org.springframework.boot.SpringApplication;
import org.springframework.core.env.Environment;
import java.util.*;
/**
* Utility class to load a Spring profile to be used as default
* when there is no <code>spring.profiles.active</code> set in the environment or as command line argument.
* If the value is not available in <code>application.yml</code> then <code>dev</code> profile will be used as default.
*/
public final class DefaultProfileUtil {
private static final String SPRING_PROFILE_DEFAULT = "spring.profiles.default";
private DefaultProfileUtil() {
}
/**
* Set a default to use when no profile is configured.
*
* @param app the Spring application
*/
public static void addDefaultProfile(SpringApplication app) {
Map<String, Object> defProperties = new HashMap<>();
/*
* The default profile to use when no other profiles are defined
* This cannot be set in the <code>application.yml</code> file.
* See https://github.com/spring-projects/spring-boot/issues/1219
*/
defProperties.put(SPRING_PROFILE_DEFAULT, JHipsterConstants.SPRING_PROFILE_DEVELOPMENT);
app.setDefaultProperties(defProperties);
}
/**
* Get the profiles that are applied else get default profiles.
*
* @param env spring environment
* @return profiles
*/
public static String[] getActiveProfiles(Environment env) {
String[] profiles = env.getActiveProfiles();
if (profiles.length == 0) {
return env.getDefaultProfiles();
}
return profiles;
}
}

View File

@ -0,0 +1,63 @@
package org.hostsharing.hsadminng.config;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.zalando.problem.ProblemModule;
import org.zalando.problem.violations.ConstraintViolationProblemModule;
@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 Hibernate5Module hibernate5Module() {
return new Hibernate5Module();
}
/*
* Jackson Afterburner module to speed up serialization/deserialization.
*/
@Bean
public AfterburnerModule afterburnerModule() {
return new AfterburnerModule();
}
/*
* Module for serialization/deserialization of RFC7807 Problem.
*/
@Bean
ProblemModule problemModule() {
return new ProblemModule();
}
/*
* Module for serialization/deserialization of ConstraintViolationProblem.
*/
@Bean
ConstraintViolationProblemModule constraintViolationProblemModule() {
return new ConstraintViolationProblemModule();
}
}

View File

@ -0,0 +1,50 @@
package org.hostsharing.hsadminng.config;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
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 org.springframework.core.task.TaskExecutor;
import io.github.jhipster.config.JHipsterConstants;
import io.github.jhipster.config.liquibase.AsyncSpringLiquibase;
import liquibase.integration.spring.SpringLiquibase;
@Configuration
public class LiquibaseConfiguration {
private final Logger log = LoggerFactory.getLogger(LiquibaseConfiguration.class);
private final Environment env;
public LiquibaseConfiguration(Environment env) {
this.env = env;
}
@Bean
public SpringLiquibase liquibase(@Qualifier("taskExecutor") TaskExecutor taskExecutor,
DataSource dataSource, LiquibaseProperties liquibaseProperties) {
// Use liquibase.integration.spring.SpringLiquibase if you don't want Liquibase to start asynchronously
SpringLiquibase liquibase = new AsyncSpringLiquibase(taskExecutor, env);
liquibase.setDataSource(dataSource);
liquibase.setChangeLog("classpath:config/liquibase/master.xml");
liquibase.setContexts(liquibaseProperties.getContexts());
liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema());
liquibase.setDropFirst(liquibaseProperties.isDropFirst());
liquibase.setChangeLogParameters(liquibaseProperties.getParameters());
if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE)) {
liquibase.setShouldRun(false);
} else {
liquibase.setShouldRun(liquibaseProperties.isEnabled());
log.debug("Configuring Liquibase");
}
return liquibase;
}
}

View File

@ -0,0 +1,27 @@
package org.hostsharing.hsadminng.config;
import io.github.jhipster.config.locale.AngularCookieLocaleResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
@Configuration
public class LocaleConfiguration implements WebMvcConfigurer {
@Bean(name = "localeResolver")
public LocaleResolver localeResolver() {
AngularCookieLocaleResolver cookieLocaleResolver = new AngularCookieLocaleResolver();
cookieLocaleResolver.setCookieName("NG_TRANSLATE_LANG_KEY");
return cookieLocaleResolver;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("language");
registry.addInterceptor(localeChangeInterceptor);
}
}

View File

@ -0,0 +1,19 @@
package org.hostsharing.hsadminng.config;
import org.hostsharing.hsadminng.aop.logging.LoggingAspect;
import io.github.jhipster.config.JHipsterConstants;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
@Configuration
@EnableAspectJAutoProxy
public class LoggingAspectConfiguration {
@Bean
@Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)
public LoggingAspect loggingAspect(Environment env) {
return new LoggingAspect(env);
}
}

View File

@ -0,0 +1,154 @@
package org.hostsharing.hsadminng.config;
import java.net.InetSocketAddress;
import java.util.Iterator;
import io.github.jhipster.config.JHipsterProperties;
import ch.qos.logback.classic.AsyncAppender;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.boolex.OnMarkerEvaluator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggerContextListener;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.filter.EvaluatorFilter;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.spi.FilterReply;
import net.logstash.logback.appender.LogstashTcpSocketAppender;
import net.logstash.logback.encoder.LogstashEncoder;
import net.logstash.logback.stacktrace.ShortenedThrowableConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LoggingConfiguration {
private static final String LOGSTASH_APPENDER_NAME = "LOGSTASH";
private static final String ASYNC_LOGSTASH_APPENDER_NAME = "ASYNC_LOGSTASH";
private final Logger log = LoggerFactory.getLogger(LoggingConfiguration.class);
private LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
private final String appName;
private final String serverPort;
private final JHipsterProperties jHipsterProperties;
public LoggingConfiguration(@Value("${spring.application.name}") String appName, @Value("${server.port}") String serverPort,
JHipsterProperties jHipsterProperties) {
this.appName = appName;
this.serverPort = serverPort;
this.jHipsterProperties = jHipsterProperties;
if (jHipsterProperties.getLogging().getLogstash().isEnabled()) {
addLogstashAppender(context);
addContextListener(context);
}
if (jHipsterProperties.getMetrics().getLogs().isEnabled()) {
setMetricsMarkerLogbackFilter(context);
}
}
private void addContextListener(LoggerContext context) {
LogbackLoggerContextListener loggerContextListener = new LogbackLoggerContextListener();
loggerContextListener.setContext(context);
context.addListener(loggerContextListener);
}
private void addLogstashAppender(LoggerContext context) {
log.info("Initializing Logstash logging");
LogstashTcpSocketAppender logstashAppender = new LogstashTcpSocketAppender();
logstashAppender.setName(LOGSTASH_APPENDER_NAME);
logstashAppender.setContext(context);
String customFields = "{\"app_name\":\"" + appName + "\",\"app_port\":\"" + serverPort + "\"}";
// More documentation is available at: https://github.com/logstash/logstash-logback-encoder
LogstashEncoder logstashEncoder = new LogstashEncoder();
// Set the Logstash appender config from JHipster properties
logstashAppender.addDestinations(new InetSocketAddress(jHipsterProperties.getLogging().getLogstash().getHost(), jHipsterProperties.getLogging().getLogstash().getPort()));
ShortenedThrowableConverter throwableConverter = new ShortenedThrowableConverter();
throwableConverter.setRootCauseFirst(true);
logstashEncoder.setThrowableConverter(throwableConverter);
logstashEncoder.setCustomFields(customFields);
logstashAppender.setEncoder(logstashEncoder);
logstashAppender.start();
// Wrap the appender in an Async appender for performance
AsyncAppender asyncLogstashAppender = new AsyncAppender();
asyncLogstashAppender.setContext(context);
asyncLogstashAppender.setName(ASYNC_LOGSTASH_APPENDER_NAME);
asyncLogstashAppender.setQueueSize(jHipsterProperties.getLogging().getLogstash().getQueueSize());
asyncLogstashAppender.addAppender(logstashAppender);
asyncLogstashAppender.start();
context.getLogger("ROOT").addAppender(asyncLogstashAppender);
}
// Configure a log filter to remove "metrics" logs from all appenders except the "LOGSTASH" appender
private void setMetricsMarkerLogbackFilter(LoggerContext context) {
log.info("Filtering metrics logs from all appenders except the {} appender", LOGSTASH_APPENDER_NAME);
OnMarkerEvaluator onMarkerMetricsEvaluator = new OnMarkerEvaluator();
onMarkerMetricsEvaluator.setContext(context);
onMarkerMetricsEvaluator.addMarker("metrics");
onMarkerMetricsEvaluator.start();
EvaluatorFilter<ILoggingEvent> metricsFilter = new EvaluatorFilter<>();
metricsFilter.setContext(context);
metricsFilter.setEvaluator(onMarkerMetricsEvaluator);
metricsFilter.setOnMatch(FilterReply.DENY);
metricsFilter.start();
for (ch.qos.logback.classic.Logger logger : context.getLoggerList()) {
for (Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders(); it.hasNext();) {
Appender<ILoggingEvent> appender = it.next();
if (!appender.getName().equals(ASYNC_LOGSTASH_APPENDER_NAME)) {
log.debug("Filter metrics logs from the {} appender", appender.getName());
appender.setContext(context);
appender.addFilter(metricsFilter);
appender.start();
}
}
}
}
/**
* Logback configuration is achieved by configuration file and API.
* When configuration file change is detected, the configuration is reset.
* This listener ensures that the programmatic configuration is also re-applied after reset.
*/
class LogbackLoggerContextListener extends ContextAwareBase implements LoggerContextListener {
@Override
public boolean isResetResistant() {
return true;
}
@Override
public void onStart(LoggerContext context) {
addLogstashAppender(context);
}
@Override
public void onReset(LoggerContext context) {
addLogstashAppender(context);
}
@Override
public void onStop(LoggerContext context) {
// Nothing to do.
}
@Override
public void onLevelChange(ch.qos.logback.classic.Logger logger, Level level) {
// Nothing to do.
}
}
}

View File

@ -0,0 +1,121 @@
package org.hostsharing.hsadminng.config;
import org.hostsharing.hsadminng.security.*;
import org.hostsharing.hsadminng.security.jwt.*;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.CorsFilter;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
import javax.annotation.PostConstruct;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final UserDetailsService userDetailsService;
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService, TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
this.authenticationManagerBuilder = authenticationManagerBuilder;
this.userDetailsService = userDetailsService;
this.tokenProvider = tokenProvider;
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
}
@PostConstruct
public void init() {
try {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
} catch (Exception e) {
throw new BeanInitializationException("Security configuration failed", e);
}
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/h2-console/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
.and()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/account/reset-password/init").permitAll()
.antMatchers("/api/account/reset-password/finish").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.and()
.apply(securityConfigurerAdapter());
}
private JWTConfigurer securityConfigurerAdapter() {
return new JWTConfigurer(tokenProvider);
}
}

View File

@ -0,0 +1,170 @@
package org.hostsharing.hsadminng.config;
import io.github.jhipster.config.JHipsterConstants;
import io.github.jhipster.config.JHipsterProperties;
import io.github.jhipster.config.h2.H2ConfigurationHelper;
import io.github.jhipster.web.filter.CachingHttpHeadersFilter;
import io.undertow.UndertowOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
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.http.MediaType;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import javax.servlet.*;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.*;
import static java.net.URLDecoder.decode;
/**
* Configuration of web application with Servlet 3.0 APIs.
*/
@Configuration
public class WebConfigurer implements ServletContextInitializer, WebServerFactoryCustomizer<WebServerFactory> {
private 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) throws ServletException {
if (env.getActiveProfiles().length != 0) {
log.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles());
}
EnumSet<DispatcherType> disps = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC);
if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) {
initCachingHttpHeadersFilter(servletContext, disps);
}
if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT)) {
initH2Console(servletContext);
}
log.info("Web application fully configured");
}
/**
* Customize the Servlet engine: Mime types, the document root, the cache.
*/
@Override
public void customize(WebServerFactory server) {
setMimeMappings(server);
// When running in an IDE or with ./gradlew bootRun, set location of the static web assets.
setLocationForStaticAssets(server);
/*
* Enable HTTP/2 for Undertow - https://twitter.com/ankinson/status/829256167700492288
* HTTP/2 requires HTTPS, so HTTP requests will fallback to HTTP/1.1.
* See the JHipsterProperties class and your application-*.yml configuration files
* for more information.
*/
if (jHipsterProperties.getHttp().getVersion().equals(JHipsterProperties.Http.Version.V_2_0) &&
server instanceof UndertowServletWebServerFactory) {
((UndertowServletWebServerFactory) server)
.addBuilderCustomizers(builder ->
builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true));
}
}
private void setMimeMappings(WebServerFactory server) {
if (server instanceof ConfigurableServletWebServerFactory) {
MimeMappings mappings = new MimeMappings(MimeMappings.DEFAULT);
// IE issue, see https://github.com/jhipster/generator-jhipster/pull/711
mappings.add("html", MediaType.TEXT_HTML_VALUE + ";charset=" + StandardCharsets.UTF_8.name().toLowerCase());
// CloudFoundry issue, see https://github.com/cloudfoundry/gorouter/issues/64
mappings.add("json", MediaType.TEXT_HTML_VALUE + ";charset=" + StandardCharsets.UTF_8.name().toLowerCase());
ConfigurableServletWebServerFactory servletWebServer = (ConfigurableServletWebServerFactory) server;
servletWebServer.setMimeMappings(mappings);
}
}
private void setLocationForStaticAssets(WebServerFactory server) {
if (server instanceof ConfigurableServletWebServerFactory) {
ConfigurableServletWebServerFactory servletWebServer = (ConfigurableServletWebServerFactory) server;
File root;
String prefixPath = resolvePathPrefix();
root = new File(prefixPath + "build/www/");
if (root.exists() && root.isDirectory()) {
servletWebServer.setDocumentRoot(root);
}
}
}
/**
* Resolve path prefix to static resources.
*/
private String resolvePathPrefix() {
String fullExecutablePath;
try {
fullExecutablePath = decode(this.getClass().getResource("").getPath(), StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
/* try without decoding if this ever happens */
fullExecutablePath = this.getClass().getResource("").getPath();
}
String rootPath = Paths.get(".").toUri().normalize().getPath();
String extractedPath = fullExecutablePath.replace(rootPath, "");
int extractionEndIndex = extractedPath.indexOf("build/");
if (extractionEndIndex <= 0) {
return "";
}
return extractedPath.substring(0, extractionEndIndex);
}
/**
* Initializes the caching HTTP Headers Filter.
*/
private void initCachingHttpHeadersFilter(ServletContext servletContext,
EnumSet<DispatcherType> disps) {
log.debug("Registering Caching HTTP Headers Filter");
FilterRegistration.Dynamic cachingHttpHeadersFilter =
servletContext.addFilter("cachingHttpHeadersFilter",
new CachingHttpHeadersFilter(jHipsterProperties));
cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/i18n/*");
cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/content/*");
cachingHttpHeadersFilter.addMappingForUrlPatterns(disps, true, "/app/*");
cachingHttpHeadersFilter.setAsyncSupported(true);
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = jHipsterProperties.getCors();
if (config.getAllowedOrigins() != null && !config.getAllowedOrigins().isEmpty()) {
log.debug("Registering CORS filter");
source.registerCorsConfiguration("/api/**", config);
source.registerCorsConfiguration("/management/**", config);
source.registerCorsConfiguration("/v2/api-docs", config);
}
return new CorsFilter(source);
}
/**
* Initializes H2 console.
*/
private void initH2Console(ServletContext servletContext) {
log.debug("Initialize H2 console");
H2ConfigurationHelper.initH2Console(servletContext);
}
}

View File

@ -0,0 +1,86 @@
package org.hostsharing.hsadminng.config.audit;
import org.hostsharing.hsadminng.domain.PersistentAuditEvent;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
public class AuditEventConverter {
/**
* Convert a list of PersistentAuditEvent to a list of AuditEvent
*
* @param persistentAuditEvents the list to convert
* @return the converted list.
*/
public List<AuditEvent> convertToAuditEvent(Iterable<PersistentAuditEvent> persistentAuditEvents) {
if (persistentAuditEvents == null) {
return Collections.emptyList();
}
List<AuditEvent> auditEvents = new ArrayList<>();
for (PersistentAuditEvent persistentAuditEvent : persistentAuditEvents) {
auditEvents.add(convertToAuditEvent(persistentAuditEvent));
}
return auditEvents;
}
/**
* Convert a PersistentAuditEvent to an AuditEvent
*
* @param persistentAuditEvent the event to convert
* @return the converted list.
*/
public AuditEvent convertToAuditEvent(PersistentAuditEvent persistentAuditEvent) {
if (persistentAuditEvent == null) {
return null;
}
return new AuditEvent(persistentAuditEvent.getAuditEventDate(), persistentAuditEvent.getPrincipal(),
persistentAuditEvent.getAuditEventType(), convertDataToObjects(persistentAuditEvent.getData()));
}
/**
* Internal conversion. This is needed to support the current SpringBoot actuator AuditEventRepository interface
*
* @param data the data to convert
* @return a map of String, Object
*/
public Map<String, Object> convertDataToObjects(Map<String, String> data) {
Map<String, Object> results = new HashMap<>();
if (data != null) {
for (Map.Entry<String, String> entry : data.entrySet()) {
results.put(entry.getKey(), entry.getValue());
}
}
return results;
}
/**
* Internal conversion. This method will allow to save additional data.
* By default, it will save the object as string
*
* @param data the data to convert
* @return a map of String, String
*/
public Map<String, String> convertDataToStrings(Map<String, Object> data) {
Map<String, String> results = new HashMap<>();
if (data != null) {
for (Map.Entry<String, Object> entry : data.entrySet()) {
// Extract the data that will be saved.
if (entry.getValue() instanceof WebAuthenticationDetails) {
WebAuthenticationDetails authenticationDetails = (WebAuthenticationDetails) entry.getValue();
results.put("remoteAddress", authenticationDetails.getRemoteAddress());
results.put("sessionId", authenticationDetails.getSessionId());
} else {
results.put(entry.getKey(), Objects.toString(entry.getValue()));
}
}
}
return results;
}
}

View File

@ -0,0 +1,4 @@
/**
* Audit specific code.
*/
package org.hostsharing.hsadminng.config.audit;

View File

@ -0,0 +1,4 @@
/**
* Spring Framework configuration files.
*/
package org.hostsharing.hsadminng.config;

View File

@ -0,0 +1,79 @@
package org.hostsharing.hsadminng.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.envers.Audited;
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;
import java.io.Serializable;
import java.time.Instant;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
/**
* Base abstract class for entities which will hold definitions for created, last modified by and created,
* last modified by date.
*/
@MappedSuperclass
@Audited
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditingEntity implements Serializable {
private static final long serialVersionUID = 1L;
@CreatedBy
@Column(name = "created_by", nullable = false, length = 50, updatable = false)
@JsonIgnore
private String createdBy;
@CreatedDate
@Column(name = "created_date", updatable = false)
@JsonIgnore
private Instant createdDate = Instant.now();
@LastModifiedBy
@Column(name = "last_modified_by", length = 50)
@JsonIgnore
private String lastModifiedBy;
@LastModifiedDate
@Column(name = "last_modified_date")
@JsonIgnore
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;
}
}

View File

@ -0,0 +1,59 @@
package org.hostsharing.hsadminng.domain;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Column;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
/**
* An authority (a security role) used by Spring Security.
*/
@Entity
@Table(name = "jhi_authority")
public class Authority implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull
@Size(max = 50)
@Id
@Column(length = 50)
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Authority authority = (Authority) o;
return !(name != null ? !name.equals(authority.name) : authority.name != null);
}
@Override
public int hashCode() {
return name != null ? name.hashCode() : 0;
}
@Override
public String toString() {
return "Authority{" +
"name='" + name + '\'' +
"}";
}
}

View File

@ -0,0 +1,110 @@
package org.hostsharing.hsadminng.domain;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.Instant;
import java.util.HashMap;
import java.util.Objects;
import java.util.Map;
/**
* Persist AuditEvent managed by the Spring Boot actuator.
*
* @see org.springframework.boot.actuate.audit.AuditEvent
*/
@Entity
@Table(name = "jhi_persistent_audit_event")
public class PersistentAuditEvent implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
@Column(name = "event_id")
private Long id;
@NotNull
@Column(nullable = false)
private String principal;
@Column(name = "event_date")
private Instant auditEventDate;
@Column(name = "event_type")
private String auditEventType;
@ElementCollection
@MapKeyColumn(name = "name")
@Column(name = "value")
@CollectionTable(name = "jhi_persistent_audit_evt_data", joinColumns=@JoinColumn(name="event_id"))
private Map<String, String> data = new HashMap<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPrincipal() {
return principal;
}
public void setPrincipal(String principal) {
this.principal = principal;
}
public Instant getAuditEventDate() {
return auditEventDate;
}
public void setAuditEventDate(Instant auditEventDate) {
this.auditEventDate = auditEventDate;
}
public String getAuditEventType() {
return auditEventType;
}
public void setAuditEventType(String auditEventType) {
this.auditEventType = auditEventType;
}
public Map<String, String> getData() {
return data;
}
public void setData(Map<String, String> data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PersistentAuditEvent persistentAuditEvent = (PersistentAuditEvent) o;
return !(persistentAuditEvent.getId() == null || getId() == null) && Objects.equals(getId(), persistentAuditEvent.getId());
}
@Override
public int hashCode() {
return Objects.hashCode(getId());
}
@Override
public String toString() {
return "PersistentAuditEvent{" +
"principal='" + principal + '\'' +
", auditEventDate=" + auditEventDate +
", auditEventType='" + auditEventType + '\'' +
'}';
}
}

View File

@ -0,0 +1,232 @@
package org.hostsharing.hsadminng.domain;
import org.hostsharing.hsadminng.config.Constants;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.BatchSize;
import javax.validation.constraints.Email;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.time.Instant;
/**
* A user.
*/
@Entity
@Table(name = "jhi_user")
public class User extends AbstractAuditingEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
private Long id;
@NotNull
@Pattern(regexp = Constants.LOGIN_REGEX)
@Size(min = 1, max = 50)
@Column(length = 50, unique = true, nullable = false)
private String login;
@JsonIgnore
@NotNull
@Size(min = 60, max = 60)
@Column(name = "password_hash", length = 60, nullable = false)
private String password;
@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 = 6)
@Column(name = "lang_key", length = 6)
private String langKey;
@Size(max = 256)
@Column(name = "image_url", length = 256)
private String imageUrl;
@Size(max = 20)
@Column(name = "activation_key", length = 20)
@JsonIgnore
private String activationKey;
@Size(max = 20)
@Column(name = "reset_key", length = 20)
@JsonIgnore
private String resetKey;
@Column(name = "reset_date")
private Instant resetDate = null;
@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 Long getId() {
return id;
}
public void setId(Long 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 getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
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 getActivated() {
return activated;
}
public void setActivated(boolean activated) {
this.activated = activated;
}
public String getActivationKey() {
return activationKey;
}
public void setActivationKey(String activationKey) {
this.activationKey = activationKey;
}
public String getResetKey() {
return resetKey;
}
public void setResetKey(String resetKey) {
this.resetKey = resetKey;
}
public Instant getResetDate() {
return resetDate;
}
public void setResetDate(Instant resetDate) {
this.resetDate = resetDate;
}
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 == null || getClass() != o.getClass()) {
return false;
}
User user = (User) o;
return !(user.getId() == null || getId() == null) && Objects.equals(getId(), user.getId());
}
@Override
public int hashCode() {
return Objects.hashCode(getId());
}
@Override
public String toString() {
return "User{" +
"login='" + login + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", imageUrl='" + imageUrl + '\'' +
", activated='" + activated + '\'' +
", langKey='" + langKey + '\'' +
", activationKey='" + activationKey + '\'' +
"}";
}
}

View File

@ -0,0 +1,4 @@
/**
* JPA domain objects.
*/
package org.hostsharing.hsadminng.domain;

View File

@ -0,0 +1,11 @@
package org.hostsharing.hsadminng.repository;
import org.hostsharing.hsadminng.domain.Authority;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Spring Data JPA repository for the Authority entity.
*/
public interface AuthorityRepository extends JpaRepository<Authority, String> {
}

View File

@ -0,0 +1,89 @@
package org.hostsharing.hsadminng.repository;
import org.hostsharing.hsadminng.config.Constants;
import org.hostsharing.hsadminng.config.audit.AuditEventConverter;
import org.hostsharing.hsadminng.domain.PersistentAuditEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.*;
/**
* An implementation of Spring Boot's AuditEventRepository.
*/
@Repository
public class CustomAuditEventRepository implements AuditEventRepository {
private static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE";
/**
* Should be the same as in Liquibase migration.
*/
protected static final int EVENT_DATA_COLUMN_MAX_LENGTH = 255;
private final PersistenceAuditEventRepository persistenceAuditEventRepository;
private final AuditEventConverter auditEventConverter;
private final Logger log = LoggerFactory.getLogger(getClass());
public CustomAuditEventRepository(PersistenceAuditEventRepository persistenceAuditEventRepository,
AuditEventConverter auditEventConverter) {
this.persistenceAuditEventRepository = persistenceAuditEventRepository;
this.auditEventConverter = auditEventConverter;
}
@Override
public List<AuditEvent> find(String principal, Instant after, String type) {
Iterable<PersistentAuditEvent> persistentAuditEvents =
persistenceAuditEventRepository.findByPrincipalAndAuditEventDateAfterAndAuditEventType(principal, after, type);
return auditEventConverter.convertToAuditEvent(persistentAuditEvents);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void add(AuditEvent event) {
if (!AUTHORIZATION_FAILURE.equals(event.getType()) &&
!Constants.ANONYMOUS_USER.equals(event.getPrincipal())) {
PersistentAuditEvent persistentAuditEvent = new PersistentAuditEvent();
persistentAuditEvent.setPrincipal(event.getPrincipal());
persistentAuditEvent.setAuditEventType(event.getType());
persistentAuditEvent.setAuditEventDate(event.getTimestamp());
Map<String, String> eventData = auditEventConverter.convertDataToStrings(event.getData());
persistentAuditEvent.setData(truncate(eventData));
persistenceAuditEventRepository.save(persistentAuditEvent);
}
}
/**
* Truncate event data that might exceed column length.
*/
private Map<String, String> truncate(Map<String, String> data) {
Map<String, String> results = new HashMap<>();
if (data != null) {
for (Map.Entry<String, String> entry : data.entrySet()) {
String value = entry.getValue();
if (value != null) {
int length = value.length();
if (length > EVENT_DATA_COLUMN_MAX_LENGTH) {
value = value.substring(0, EVENT_DATA_COLUMN_MAX_LENGTH);
log.warn("Event data for {} too long ({}) has been truncated to {}. Consider increasing column width.",
entry.getKey(), length, EVENT_DATA_COLUMN_MAX_LENGTH);
}
}
results.put(entry.getKey(), value);
}
}
return results;
}
}

View File

@ -0,0 +1,25 @@
package org.hostsharing.hsadminng.repository;
import org.hostsharing.hsadminng.domain.PersistentAuditEvent;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.Instant;
import java.util.List;
/**
* Spring Data JPA repository for the PersistentAuditEvent entity.
*/
public interface PersistenceAuditEventRepository extends JpaRepository<PersistentAuditEvent, Long> {
List<PersistentAuditEvent> findByPrincipal(String principal);
List<PersistentAuditEvent> findByAuditEventDateAfter(Instant after);
List<PersistentAuditEvent> findByPrincipalAndAuditEventDateAfter(String principal, Instant after);
List<PersistentAuditEvent> findByPrincipalAndAuditEventDateAfterAndAuditEventType(String principal, Instant after, String type);
Page<PersistentAuditEvent> findAllByAuditEventDateBetween(Instant fromDate, Instant toDate, Pageable pageable);
}

View File

@ -0,0 +1,47 @@
package org.hostsharing.hsadminng.repository;
import org.hostsharing.hsadminng.domain.User;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import java.time.Instant;
/**
* Spring Data JPA repository for the User entity.
*/
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
String USERS_BY_LOGIN_CACHE = "usersByLogin";
String USERS_BY_EMAIL_CACHE = "usersByEmail";
Optional<User> findOneByActivationKey(String activationKey);
List<User> findAllByActivatedIsFalseAndCreatedDateBefore(Instant dateTime);
Optional<User> findOneByResetKey(String resetKey);
Optional<User> findOneByEmailIgnoreCase(String email);
Optional<User> findOneByLogin(String login);
@EntityGraph(attributePaths = "authorities")
Optional<User> findOneWithAuthoritiesById(Long id);
@EntityGraph(attributePaths = "authorities")
@Cacheable(cacheNames = USERS_BY_LOGIN_CACHE)
Optional<User> findOneWithAuthoritiesByLogin(String login);
@EntityGraph(attributePaths = "authorities")
@Cacheable(cacheNames = USERS_BY_EMAIL_CACHE)
Optional<User> findOneWithAuthoritiesByEmail(String email);
Page<User> findAllByLoginNot(Pageable pageable, String login);
}

View File

@ -0,0 +1,4 @@
/**
* Spring Data JPA repositories.
*/
package org.hostsharing.hsadminng.repository;

View File

@ -0,0 +1,16 @@
package org.hostsharing.hsadminng.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() {
}
}

View File

@ -0,0 +1,62 @@
package org.hostsharing.hsadminng.security;
import org.hostsharing.hsadminng.domain.User;
import org.hostsharing.hsadminng.repository.UserRepository;
import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
/**
* Authenticate a user from the database.
*/
@Component("userDetailsService")
public class DomainUserDetailsService implements UserDetailsService {
private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class);
private final UserRepository userRepository;
public DomainUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
@Transactional
public UserDetails loadUserByUsername(final String login) {
log.debug("Authenticating {}", login);
if (new EmailValidator().isValid(login, null)) {
return userRepository.findOneWithAuthoritiesByEmail(login)
.map(user -> createSpringSecurityUser(login, user))
.orElseThrow(() -> new UsernameNotFoundException("User with email " + login + " was not found in the database"));
}
String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
return userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin)
.map(user -> createSpringSecurityUser(lowercaseLogin, user))
.orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database"));
}
private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) {
if (!user.getActivated()) {
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
}
List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getName()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(user.getLogin(),
user.getPassword(),
grantedAuthorities);
}
}

View File

@ -0,0 +1,76 @@
package org.hostsharing.hsadminng.security;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Optional;
/**
* Utility class for Spring Security.
*/
public final class SecurityUtils {
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(securityContext.getAuthentication())
.map(authentication -> {
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
return springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
return (String) authentication.getPrincipal();
}
return null;
});
}
/**
* Get the JWT of the current user.
*
* @return the JWT of the current user
*/
public static Optional<String> getCurrentUserJWT() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication())
.filter(authentication -> authentication.getCredentials() instanceof String)
.map(authentication -> (String) authentication.getCredentials());
}
/**
* Check if a user is authenticated.
*
* @return true if the user is authenticated, false otherwise
*/
public static boolean isAuthenticated() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication())
.map(authentication -> authentication.getAuthorities().stream()
.noneMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(AuthoritiesConstants.ANONYMOUS)))
.orElse(false);
}
/**
* If the current user has a specific authority (security role).
* <p>
* The name of this method comes from the isUserInRole() method in the Servlet API
*
* @param authority the authority to check
* @return true if the current user has the authority, false otherwise
*/
public static boolean isCurrentUserInRole(String authority) {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication())
.map(authentication -> authentication.getAuthorities().stream()
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority)))
.orElse(false);
}
}

View File

@ -0,0 +1,20 @@
package org.hostsharing.hsadminng.security;
import org.hostsharing.hsadminng.config.Constants;
import java.util.Optional;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
/**
* Implementation of 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_ACCOUNT));
}
}

View File

@ -0,0 +1,19 @@
package org.hostsharing.hsadminng.security;
import org.springframework.security.core.AuthenticationException;
/**
* This exception is thrown in case of a not activated user trying to authenticate.
*/
public class UserNotActivatedException extends AuthenticationException {
private static final long serialVersionUID = 1L;
public UserNotActivatedException(String message) {
super(message);
}
public UserNotActivatedException(String message, Throwable t) {
super(message, t);
}
}

View File

@ -0,0 +1,21 @@
package org.hostsharing.hsadminng.security.jwt;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
public class JWTConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private TokenProvider tokenProvider;
public JWTConfigurer(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void configure(HttpSecurity http) throws Exception {
JWTFilter customFilter = new JWTFilter(tokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}

View File

@ -0,0 +1,48 @@
package org.hostsharing.hsadminng.security.jwt;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is
* found.
*/
public class JWTFilter extends GenericFilterBean {
public static final String AUTHORIZATION_HEADER = "Authorization";
private TokenProvider tokenProvider;
public JWTFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = resolveToken(httpServletRequest);
if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) {
Authentication authentication = this.tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private String resolveToken(HttpServletRequest request){
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}

View File

@ -0,0 +1,119 @@
package org.hostsharing.hsadminng.security.jwt;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import io.github.jhipster.config.JHipsterProperties;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
@Component
public class TokenProvider {
private final Logger log = LoggerFactory.getLogger(TokenProvider.class);
private static final String AUTHORITIES_KEY = "auth";
private Key key;
private long tokenValidityInMilliseconds;
private long tokenValidityInMillisecondsForRememberMe;
private final JHipsterProperties jHipsterProperties;
public TokenProvider(JHipsterProperties jHipsterProperties) {
this.jHipsterProperties = jHipsterProperties;
}
@PostConstruct
public void init() {
byte[] keyBytes;
String secret = jHipsterProperties.getSecurity().getAuthentication().getJwt().getSecret();
if (!StringUtils.isEmpty(secret)) {
log.warn("Warning: the JWT key used is not Base64-encoded. " +
"We recommend using the `jhipster.security.authentication.jwt.base64-secret` key for optimum security.");
keyBytes = secret.getBytes(StandardCharsets.UTF_8);
} else {
log.debug("Using a Base64-encoded JWT secret key");
keyBytes = Decoders.BASE64.decode(jHipsterProperties.getSecurity().getAuthentication().getJwt().getBase64Secret());
}
this.key = Keys.hmacShaKeyFor(keyBytes);
this.tokenValidityInMilliseconds =
1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt().getTokenValidityInSeconds();
this.tokenValidityInMillisecondsForRememberMe =
1000 * jHipsterProperties.getSecurity().getAuthentication().getJwt()
.getTokenValidityInSecondsForRememberMe();
}
public String createToken(Authentication authentication, boolean rememberMe) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
Date validity;
if (rememberMe) {
validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe);
} else {
validity = new Date(now + this.tokenValidityInMilliseconds);
}
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(key).parseClaimsJws(authToken);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("Invalid JWT signature.");
log.trace("Invalid JWT signature trace: {}", e);
} catch (ExpiredJwtException e) {
log.info("Expired JWT token.");
log.trace("Expired JWT token trace: {}", e);
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT token.");
log.trace("Unsupported JWT token trace: {}", e);
} catch (IllegalArgumentException e) {
log.info("JWT token compact of handler are invalid.");
log.trace("JWT token compact of handler are invalid trace: {}", e);
}
return false;
}
}

View File

@ -0,0 +1,4 @@
/**
* Spring Security configuration.
*/
package org.hostsharing.hsadminng.security;

View File

@ -0,0 +1,51 @@
package org.hostsharing.hsadminng.service;
import org.hostsharing.hsadminng.config.audit.AuditEventConverter;
import org.hostsharing.hsadminng.repository.PersistenceAuditEventRepository;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.Optional;
/**
* Service for managing audit events.
* <p>
* This is the default implementation to support SpringBoot Actuator AuditEventRepository
*/
@Service
@Transactional
public class AuditEventService {
private final PersistenceAuditEventRepository persistenceAuditEventRepository;
private final AuditEventConverter auditEventConverter;
public AuditEventService(
PersistenceAuditEventRepository persistenceAuditEventRepository,
AuditEventConverter auditEventConverter) {
this.persistenceAuditEventRepository = persistenceAuditEventRepository;
this.auditEventConverter = auditEventConverter;
}
public Page<AuditEvent> findAll(Pageable pageable) {
return persistenceAuditEventRepository.findAll(pageable)
.map(auditEventConverter::convertToAuditEvent);
}
public Page<AuditEvent> findByDates(Instant fromDate, Instant toDate, Pageable pageable) {
return persistenceAuditEventRepository.findAllByAuditEventDateBetween(fromDate, toDate, pageable)
.map(auditEventConverter::convertToAuditEvent);
}
public Optional<AuditEvent> find(Long id) {
return Optional.ofNullable(persistenceAuditEventRepository.findById(id))
.filter(Optional::isPresent)
.map(Optional::get)
.map(auditEventConverter::convertToAuditEvent);
}
}

View File

@ -0,0 +1,105 @@
package org.hostsharing.hsadminng.service;
import org.hostsharing.hsadminng.domain.User;
import io.github.jhipster.config.JHipsterProperties;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import javax.mail.internet.MimeMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;
/**
* Service for sending emails.
* <p>
* We use the @Async annotation to send emails asynchronously.
*/
@Service
public class MailService {
private final Logger log = LoggerFactory.getLogger(MailService.class);
private static final String USER = "user";
private static final String BASE_URL = "baseUrl";
private final JHipsterProperties jHipsterProperties;
private final JavaMailSender javaMailSender;
private final MessageSource messageSource;
private final SpringTemplateEngine templateEngine;
public MailService(JHipsterProperties jHipsterProperties, JavaMailSender javaMailSender,
MessageSource messageSource, SpringTemplateEngine templateEngine) {
this.jHipsterProperties = jHipsterProperties;
this.javaMailSender = javaMailSender;
this.messageSource = messageSource;
this.templateEngine = templateEngine;
}
@Async
public void sendEmail(String to, String subject, String content, boolean isMultipart, boolean isHtml) {
log.debug("Send email[multipart '{}' and html '{}'] to '{}' with subject '{}' and content={}",
isMultipart, isHtml, to, subject, content);
// Prepare message using a Spring helper
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
try {
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, isMultipart, StandardCharsets.UTF_8.name());
message.setTo(to);
message.setFrom(jHipsterProperties.getMail().getFrom());
message.setSubject(subject);
message.setText(content, isHtml);
javaMailSender.send(mimeMessage);
log.debug("Sent email to User '{}'", to);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.warn("Email could not be sent to user '{}'", to, e);
} else {
log.warn("Email could not be sent to user '{}': {}", to, e.getMessage());
}
}
}
@Async
public void sendEmailFromTemplate(User user, String templateName, String titleKey) {
Locale locale = Locale.forLanguageTag(user.getLangKey());
Context context = new Context(locale);
context.setVariable(USER, user);
context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl());
String content = templateEngine.process(templateName, context);
String subject = messageSource.getMessage(titleKey, null, locale);
sendEmail(user.getEmail(), subject, content, false, true);
}
@Async
public void sendActivationEmail(User user) {
log.debug("Sending activation email to '{}'", user.getEmail());
sendEmailFromTemplate(user, "mail/activationEmail", "email.activation.title");
}
@Async
public void sendCreationEmail(User user) {
log.debug("Sending creation email to '{}'", user.getEmail());
sendEmailFromTemplate(user, "mail/creationEmail", "email.activation.title");
}
@Async
public void sendPasswordResetMail(User user) {
log.debug("Sending password reset email to '{}'", user.getEmail());
sendEmailFromTemplate(user, "mail/passwordResetEmail", "email.reset.title");
}
}

View File

@ -0,0 +1,294 @@
package org.hostsharing.hsadminng.service;
import org.hostsharing.hsadminng.config.Constants;
import org.hostsharing.hsadminng.domain.Authority;
import org.hostsharing.hsadminng.domain.User;
import org.hostsharing.hsadminng.repository.AuthorityRepository;
import org.hostsharing.hsadminng.repository.UserRepository;
import org.hostsharing.hsadminng.security.AuthoritiesConstants;
import org.hostsharing.hsadminng.security.SecurityUtils;
import org.hostsharing.hsadminng.service.dto.UserDTO;
import org.hostsharing.hsadminng.service.util.RandomUtil;
import org.hostsharing.hsadminng.web.rest.errors.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.CacheManager;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
/**
* Service class for managing users.
*/
@Service
@Transactional
public class UserService {
private final Logger log = LoggerFactory.getLogger(UserService.class);
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final AuthorityRepository authorityRepository;
private final CacheManager cacheManager;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthorityRepository authorityRepository, CacheManager cacheManager) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.authorityRepository = authorityRepository;
this.cacheManager = cacheManager;
}
public Optional<User> activateRegistration(String key) {
log.debug("Activating user for activation key {}", key);
return userRepository.findOneByActivationKey(key)
.map(user -> {
// activate given user for the registration key.
user.setActivated(true);
user.setActivationKey(null);
this.clearUserCaches(user);
log.debug("Activated user: {}", user);
return user;
});
}
public Optional<User> completePasswordReset(String newPassword, String key) {
log.debug("Reset user password for reset key {}", key);
return userRepository.findOneByResetKey(key)
.filter(user -> user.getResetDate().isAfter(Instant.now().minusSeconds(86400)))
.map(user -> {
user.setPassword(passwordEncoder.encode(newPassword));
user.setResetKey(null);
user.setResetDate(null);
this.clearUserCaches(user);
return user;
});
}
public Optional<User> requestPasswordReset(String mail) {
return userRepository.findOneByEmailIgnoreCase(mail)
.filter(User::getActivated)
.map(user -> {
user.setResetKey(RandomUtil.generateResetKey());
user.setResetDate(Instant.now());
this.clearUserCaches(user);
return user;
});
}
public User registerUser(UserDTO userDTO, String password) {
userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).ifPresent(existingUser -> {
boolean removed = removeNonActivatedUser(existingUser);
if (!removed) {
throw new LoginAlreadyUsedException();
}
});
userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).ifPresent(existingUser -> {
boolean removed = removeNonActivatedUser(existingUser);
if (!removed) {
throw new EmailAlreadyUsedException();
}
});
User newUser = new User();
String encryptedPassword = passwordEncoder.encode(password);
newUser.setLogin(userDTO.getLogin().toLowerCase());
// new user gets initially a generated password
newUser.setPassword(encryptedPassword);
newUser.setFirstName(userDTO.getFirstName());
newUser.setLastName(userDTO.getLastName());
newUser.setEmail(userDTO.getEmail().toLowerCase());
newUser.setImageUrl(userDTO.getImageUrl());
newUser.setLangKey(userDTO.getLangKey());
// new user is not active
newUser.setActivated(false);
// new user gets registration key
newUser.setActivationKey(RandomUtil.generateActivationKey());
Set<Authority> authorities = new HashSet<>();
authorityRepository.findById(AuthoritiesConstants.USER).ifPresent(authorities::add);
newUser.setAuthorities(authorities);
userRepository.save(newUser);
this.clearUserCaches(newUser);
log.debug("Created Information for User: {}", newUser);
return newUser;
}
private boolean removeNonActivatedUser(User existingUser){
if (existingUser.getActivated()) {
return false;
}
userRepository.delete(existingUser);
userRepository.flush();
this.clearUserCaches(existingUser);
return true;
}
public User createUser(UserDTO userDTO) {
User user = new User();
user.setLogin(userDTO.getLogin().toLowerCase());
user.setFirstName(userDTO.getFirstName());
user.setLastName(userDTO.getLastName());
user.setEmail(userDTO.getEmail().toLowerCase());
user.setImageUrl(userDTO.getImageUrl());
if (userDTO.getLangKey() == null) {
user.setLangKey(Constants.DEFAULT_LANGUAGE); // default language
} else {
user.setLangKey(userDTO.getLangKey());
}
String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword());
user.setPassword(encryptedPassword);
user.setResetKey(RandomUtil.generateResetKey());
user.setResetDate(Instant.now());
user.setActivated(true);
if (userDTO.getAuthorities() != null) {
Set<Authority> authorities = userDTO.getAuthorities().stream()
.map(authorityRepository::findById)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toSet());
user.setAuthorities(authorities);
}
userRepository.save(user);
this.clearUserCaches(user);
log.debug("Created Information for User: {}", user);
return user;
}
/**
* 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);
user.setEmail(email.toLowerCase());
user.setLangKey(langKey);
user.setImageUrl(imageUrl);
this.clearUserCaches(user);
log.debug("Changed Information for User: {}", user);
});
}
/**
* Update all information for a specific user, and return the modified user.
*
* @param userDTO user to update
* @return updated user
*/
public Optional<UserDTO> updateUser(UserDTO userDTO) {
return Optional.of(userRepository
.findById(userDTO.getId()))
.filter(Optional::isPresent)
.map(Optional::get)
.map(user -> {
this.clearUserCaches(user);
user.setLogin(userDTO.getLogin().toLowerCase());
user.setFirstName(userDTO.getFirstName());
user.setLastName(userDTO.getLastName());
user.setEmail(userDTO.getEmail().toLowerCase());
user.setImageUrl(userDTO.getImageUrl());
user.setActivated(userDTO.isActivated());
user.setLangKey(userDTO.getLangKey());
Set<Authority> managedAuthorities = user.getAuthorities();
managedAuthorities.clear();
userDTO.getAuthorities().stream()
.map(authorityRepository::findById)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(managedAuthorities::add);
this.clearUserCaches(user);
log.debug("Changed Information for User: {}", user);
return user;
})
.map(UserDTO::new);
}
public void deleteUser(String login) {
userRepository.findOneByLogin(login).ifPresent(user -> {
userRepository.delete(user);
this.clearUserCaches(user);
log.debug("Deleted User: {}", user);
});
}
public void changePassword(String currentClearTextPassword, String newPassword) {
SecurityUtils.getCurrentUserLogin()
.flatMap(userRepository::findOneByLogin)
.ifPresent(user -> {
String currentEncryptedPassword = user.getPassword();
if (!passwordEncoder.matches(currentClearTextPassword, currentEncryptedPassword)) {
throw new InvalidPasswordException();
}
String encryptedPassword = passwordEncoder.encode(newPassword);
user.setPassword(encryptedPassword);
this.clearUserCaches(user);
log.debug("Changed password for User: {}", user);
});
}
@Transactional(readOnly = true)
public Page<UserDTO> getAllManagedUsers(Pageable pageable) {
return userRepository.findAllByLoginNot(pageable, Constants.ANONYMOUS_USER).map(UserDTO::new);
}
@Transactional(readOnly = true)
public Optional<User> getUserWithAuthoritiesByLogin(String login) {
return userRepository.findOneWithAuthoritiesByLogin(login);
}
@Transactional(readOnly = true)
public Optional<User> getUserWithAuthorities(Long id) {
return userRepository.findOneWithAuthoritiesById(id);
}
@Transactional(readOnly = true)
public Optional<User> getUserWithAuthorities() {
return SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneWithAuthoritiesByLogin);
}
/**
* Not activated users should be automatically deleted after 3 days.
* <p>
* This is scheduled to get fired everyday, at 01:00 (am).
*/
@Scheduled(cron = "0 0 1 * * ?")
public void removeNotActivatedUsers() {
userRepository
.findAllByActivatedIsFalseAndCreatedDateBefore(Instant.now().minus(3, ChronoUnit.DAYS))
.forEach(user -> {
log.debug("Deleting not activated user {}", user.getLogin());
userRepository.delete(user);
this.clearUserCaches(user);
});
}
/**
* @return a list of all the authorities
*/
public List<String> getAuthorities() {
return authorityRepository.findAll().stream().map(Authority::getName).collect(Collectors.toList());
}
private void clearUserCaches(User user) {
Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE)).evict(user.getLogin());
Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE)).evict(user.getEmail());
}
}

View File

@ -0,0 +1,35 @@
package org.hostsharing.hsadminng.service.dto;
/**
* A DTO representing a password change required data - current and new password.
*/
public class PasswordChangeDTO {
private String currentPassword;
private String newPassword;
public PasswordChangeDTO() {
// Empty constructor needed for Jackson.
}
public PasswordChangeDTO(String currentPassword, String newPassword) {
this.currentPassword = currentPassword;
this.newPassword = newPassword;
}
public String getCurrentPassword() {
return currentPassword;
}
public void setCurrentPassword(String currentPassword) {
this.currentPassword = currentPassword;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}

View File

@ -0,0 +1,199 @@
package org.hostsharing.hsadminng.service.dto;
import org.hostsharing.hsadminng.config.Constants;
import org.hostsharing.hsadminng.domain.Authority;
import org.hostsharing.hsadminng.domain.User;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.*;
import java.time.Instant;
import java.util.Set;
import java.util.stream.Collectors;
/**
* A DTO representing a user, with his authorities.
*/
public class UserDTO {
private Long 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 = 6)
private String langKey;
private String createdBy;
private Instant createdDate;
private String lastModifiedBy;
private Instant lastModifiedDate;
private Set<String> authorities;
public UserDTO() {
// Empty constructor needed for Jackson.
}
public UserDTO(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.getActivated();
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 Long getId() {
return id;
}
public void setId(Long 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;
}
@Override
public String toString() {
return "UserDTO{" +
"login='" + login + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", imageUrl='" + imageUrl + '\'' +
", activated=" + activated +
", langKey='" + langKey + '\'' +
", createdBy=" + createdBy +
", createdDate=" + createdDate +
", lastModifiedBy='" + lastModifiedBy + '\'' +
", lastModifiedDate=" + lastModifiedDate +
", authorities=" + authorities +
"}";
}
}

View File

@ -0,0 +1,4 @@
/**
* Data Transfer Objects.
*/
package org.hostsharing.hsadminng.service.dto;

View File

@ -0,0 +1,81 @@
package org.hostsharing.hsadminng.service.mapper;
import org.hostsharing.hsadminng.domain.Authority;
import org.hostsharing.hsadminng.domain.User;
import org.hostsharing.hsadminng.service.dto.UserDTO;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
/**
* Mapper for the entity User and its DTO called 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)
.collect(Collectors.toList());
}
public UserDTO userToUserDTO(User user) {
return new UserDTO(user);
}
public List<User> userDTOsToUsers(List<UserDTO> userDTOs) {
return userDTOs.stream()
.filter(Objects::nonNull)
.map(this::userDTOToUser)
.collect(Collectors.toList());
}
public User userDTOToUser(UserDTO 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.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(Long id) {
if (id == null) {
return null;
}
User user = new User();
user.setId(id);
return user;
}
}

View File

@ -0,0 +1,4 @@
/**
* MapStruct mappers for mapping domain objects and Data Transfer Objects.
*/
package org.hostsharing.hsadminng.service.mapper;

View File

@ -0,0 +1,4 @@
/**
* Service layer beans.
*/
package org.hostsharing.hsadminng.service;

View File

@ -0,0 +1,41 @@
package org.hostsharing.hsadminng.service.util;
import org.apache.commons.lang3.RandomStringUtils;
/**
* Utility class for generating random Strings.
*/
public final class RandomUtil {
private static final int DEF_COUNT = 20;
private RandomUtil() {
}
/**
* Generate a password.
*
* @return the generated password
*/
public static String generatePassword() {
return RandomStringUtils.randomAlphanumeric(DEF_COUNT);
}
/**
* Generate an activation key.
*
* @return the generated activation key
*/
public static String generateActivationKey() {
return RandomStringUtils.randomNumeric(DEF_COUNT);
}
/**
* Generate a reset key.
*
* @return the generated reset key
*/
public static String generateResetKey() {
return RandomStringUtils.randomNumeric(DEF_COUNT);
}
}

View File

@ -0,0 +1,179 @@
package org.hostsharing.hsadminng.web.rest;
import org.hostsharing.hsadminng.domain.User;
import org.hostsharing.hsadminng.repository.UserRepository;
import org.hostsharing.hsadminng.security.SecurityUtils;
import org.hostsharing.hsadminng.service.MailService;
import org.hostsharing.hsadminng.service.UserService;
import org.hostsharing.hsadminng.service.dto.PasswordChangeDTO;
import org.hostsharing.hsadminng.service.dto.UserDTO;
import org.hostsharing.hsadminng.web.rest.errors.*;
import org.hostsharing.hsadminng.web.rest.vm.KeyAndPasswordVM;
import org.hostsharing.hsadminng.web.rest.vm.ManagedUserVM;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.*;
/**
* REST controller for managing the current user's account.
*/
@RestController
@RequestMapping("/api")
public class AccountResource {
private final Logger log = LoggerFactory.getLogger(AccountResource.class);
private final UserRepository userRepository;
private final UserService userService;
private final MailService mailService;
public AccountResource(UserRepository userRepository, UserService userService, MailService mailService) {
this.userRepository = userRepository;
this.userService = userService;
this.mailService = mailService;
}
/**
* POST /register : register the user.
*
* @param managedUserVM the managed user View Model
* @throws InvalidPasswordException 400 (Bad Request) if the password is incorrect
* @throws EmailAlreadyUsedException 400 (Bad Request) if the email is already used
* @throws LoginAlreadyUsedException 400 (Bad Request) if the login is already used
*/
@PostMapping("/register")
@ResponseStatus(HttpStatus.CREATED)
public void registerAccount(@Valid @RequestBody ManagedUserVM managedUserVM) {
if (!checkPasswordLength(managedUserVM.getPassword())) {
throw new InvalidPasswordException();
}
User user = userService.registerUser(managedUserVM, managedUserVM.getPassword());
mailService.sendActivationEmail(user);
}
/**
* GET /activate : activate the registered user.
*
* @param key the activation key
* @throws RuntimeException 500 (Internal Server Error) if the user couldn't be activated
*/
@GetMapping("/activate")
public void activateAccount(@RequestParam(value = "key") String key) {
Optional<User> user = userService.activateRegistration(key);
if (!user.isPresent()) {
throw new InternalServerErrorException("No user was found for this activation key");
}
}
/**
* GET /authenticate : check if the user is authenticated, and return its login.
*
* @param request the HTTP request
* @return the login if the user is authenticated
*/
@GetMapping("/authenticate")
public String isAuthenticated(HttpServletRequest request) {
log.debug("REST request to check if the current user is authenticated");
return request.getRemoteUser();
}
/**
* GET /account : get the current user.
*
* @return the current user
* @throws RuntimeException 500 (Internal Server Error) if the user couldn't be returned
*/
@GetMapping("/account")
public UserDTO getAccount() {
return userService.getUserWithAuthorities()
.map(UserDTO::new)
.orElseThrow(() -> new InternalServerErrorException("User could not be found"));
}
/**
* POST /account : update the current user information.
*
* @param userDTO the current user information
* @throws EmailAlreadyUsedException 400 (Bad Request) if the email is already used
* @throws RuntimeException 500 (Internal Server Error) if the user login wasn't found
*/
@PostMapping("/account")
public void saveAccount(@Valid @RequestBody UserDTO userDTO) {
String userLogin = SecurityUtils.getCurrentUserLogin().orElseThrow(() -> new InternalServerErrorException("Current user login not found"));
Optional<User> existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail());
if (existingUser.isPresent() && (!existingUser.get().getLogin().equalsIgnoreCase(userLogin))) {
throw new EmailAlreadyUsedException();
}
Optional<User> user = userRepository.findOneByLogin(userLogin);
if (!user.isPresent()) {
throw new InternalServerErrorException("User could not be found");
}
userService.updateUser(userDTO.getFirstName(), userDTO.getLastName(), userDTO.getEmail(),
userDTO.getLangKey(), userDTO.getImageUrl());
}
/**
* POST /account/change-password : changes the current user's password
*
* @param passwordChangeDto current and new password
* @throws InvalidPasswordException 400 (Bad Request) if the new password is incorrect
*/
@PostMapping(path = "/account/change-password")
public void changePassword(@RequestBody PasswordChangeDTO passwordChangeDto) {
if (!checkPasswordLength(passwordChangeDto.getNewPassword())) {
throw new InvalidPasswordException();
}
userService.changePassword(passwordChangeDto.getCurrentPassword(), passwordChangeDto.getNewPassword());
}
/**
* POST /account/reset-password/init : Send an email to reset the password of the user
*
* @param mail the mail of the user
* @throws EmailNotFoundException 400 (Bad Request) if the email address is not registered
*/
@PostMapping(path = "/account/reset-password/init")
public void requestPasswordReset(@RequestBody String mail) {
mailService.sendPasswordResetMail(
userService.requestPasswordReset(mail)
.orElseThrow(EmailNotFoundException::new)
);
}
/**
* POST /account/reset-password/finish : Finish to reset the password of the user
*
* @param keyAndPassword the generated key and the new password
* @throws InvalidPasswordException 400 (Bad Request) if the password is incorrect
* @throws RuntimeException 500 (Internal Server Error) if the password could not be reset
*/
@PostMapping(path = "/account/reset-password/finish")
public void finishPasswordReset(@RequestBody KeyAndPasswordVM keyAndPassword) {
if (!checkPasswordLength(keyAndPassword.getNewPassword())) {
throw new InvalidPasswordException();
}
Optional<User> user =
userService.completePasswordReset(keyAndPassword.getNewPassword(), keyAndPassword.getKey());
if (!user.isPresent()) {
throw new InternalServerErrorException("No user was found for this reset key");
}
}
private static boolean checkPasswordLength(String password) {
return !StringUtils.isEmpty(password) &&
password.length() >= ManagedUserVM.PASSWORD_MIN_LENGTH &&
password.length() <= ManagedUserVM.PASSWORD_MAX_LENGTH;
}
}

View File

@ -0,0 +1,77 @@
package org.hostsharing.hsadminng.web.rest;
import org.hostsharing.hsadminng.service.AuditEventService;
import org.hostsharing.hsadminng.web.rest.util.PaginationUtil;
import io.github.jhipster.web.util.ResponseUtil;
import org.springframework.boot.actuate.audit.AuditEvent;
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 java.time.LocalDate;
import java.time.ZoneId;
import java.util.List;
/**
* REST controller for getting the audit events.
*/
@RestController
@RequestMapping("/management/audits")
public class AuditResource {
private final AuditEventService auditEventService;
public AuditResource(AuditEventService auditEventService) {
this.auditEventService = auditEventService;
}
/**
* GET /audits : get a page of AuditEvents.
*
* @param pageable the pagination information
* @return the ResponseEntity with status 200 (OK) and the list of AuditEvents in body
*/
@GetMapping
public ResponseEntity<List<AuditEvent>> getAll(Pageable pageable) {
Page<AuditEvent> page = auditEventService.findAll(pageable);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/management/audits");
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
}
/**
* GET /audits : get a page of AuditEvents between the fromDate and toDate.
*
* @param fromDate the start of the time period of AuditEvents to get
* @param toDate the end of the time period of AuditEvents to get
* @param pageable the pagination information
* @return the ResponseEntity with status 200 (OK) and the list of AuditEvents in body
*/
@GetMapping(params = {"fromDate", "toDate"})
public ResponseEntity<List<AuditEvent>> getByDates(
@RequestParam(value = "fromDate") LocalDate fromDate,
@RequestParam(value = "toDate") LocalDate toDate,
Pageable pageable) {
Page<AuditEvent> page = auditEventService.findByDates(
fromDate.atStartOfDay(ZoneId.systemDefault()).toInstant(),
toDate.atStartOfDay(ZoneId.systemDefault()).plusDays(1).toInstant(),
pageable);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/management/audits");
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
}
/**
* GET /audits/:id : get an AuditEvent by id.
*
* @param id the id of the entity to get
* @return the ResponseEntity with status 200 (OK) and the AuditEvent in body, or status 404 (Not Found)
*/
@GetMapping("/{id:.+}")
public ResponseEntity<AuditEvent> get(@PathVariable Long id) {
return ResponseUtil.wrapOrNotFound(auditEventService.find(id));
}
}

View File

@ -0,0 +1,36 @@
package org.hostsharing.hsadminng.web.rest;
import org.hostsharing.hsadminng.web.rest.vm.LoggerVM;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* Controller for view and managing Log Level at runtime.
*/
@RestController
@RequestMapping("/management")
public class LogsResource {
@GetMapping("/logs")
public List<LoggerVM> getList() {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
return context.getLoggerList()
.stream()
.map(LoggerVM::new)
.collect(Collectors.toList());
}
@PutMapping("/logs")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void changeLevel(@RequestBody LoggerVM jsonLogger) {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
context.getLogger(jsonLogger.getName()).setLevel(Level.valueOf(jsonLogger.getLevel()));
}
}

View File

@ -0,0 +1,71 @@
package org.hostsharing.hsadminng.web.rest;
import org.hostsharing.hsadminng.security.jwt.JWTFilter;
import org.hostsharing.hsadminng.security.jwt.TokenProvider;
import org.hostsharing.hsadminng.web.rest.vm.LoginVM;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* Controller to authenticate users.
*/
@RestController
@RequestMapping("/api")
public class UserJWTController {
private final TokenProvider tokenProvider;
private final AuthenticationManager authenticationManager;
public UserJWTController(TokenProvider tokenProvider, AuthenticationManager authenticationManager) {
this.tokenProvider = tokenProvider;
this.authenticationManager = authenticationManager;
}
@PostMapping("/authenticate")
public ResponseEntity<JWTToken> authorize(@Valid @RequestBody LoginVM loginVM) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword());
Authentication authentication = this.authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
boolean rememberMe = (loginVM.isRememberMe() == null) ? false : loginVM.isRememberMe();
String jwt = tokenProvider.createToken(authentication, rememberMe);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK);
}
/**
* Object to return as body in JWT Authentication.
*/
static class JWTToken {
private String idToken;
JWTToken(String idToken) {
this.idToken = idToken;
}
@JsonProperty("id_token")
String getIdToken() {
return idToken;
}
void setIdToken(String idToken) {
this.idToken = idToken;
}
}
}

View File

@ -0,0 +1,183 @@
package org.hostsharing.hsadminng.web.rest;
import org.hostsharing.hsadminng.config.Constants;
import org.hostsharing.hsadminng.domain.User;
import org.hostsharing.hsadminng.repository.UserRepository;
import org.hostsharing.hsadminng.security.AuthoritiesConstants;
import org.hostsharing.hsadminng.service.MailService;
import org.hostsharing.hsadminng.service.UserService;
import org.hostsharing.hsadminng.service.dto.UserDTO;
import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException;
import org.hostsharing.hsadminng.web.rest.errors.EmailAlreadyUsedException;
import org.hostsharing.hsadminng.web.rest.errors.LoginAlreadyUsedException;
import org.hostsharing.hsadminng.web.rest.util.HeaderUtil;
import org.hostsharing.hsadminng.web.rest.util.PaginationUtil;
import io.github.jhipster.web.util.ResponseUtil;
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.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
/**
* REST controller for managing users.
* <p>
* This class accesses the User entity, and needs to fetch its collection of authorities.
* <p>
* For a normal use-case, it would be better to have an eager relationship between User and Authority,
* and send everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join
* which would be good for performance.
* <p>
* We use a View Model and a DTO for 3 reasons:
* <ul>
* <li>We want to keep a lazy association between the user and the authorities, because people will
* quite often do relationships with the user, and we don't want them to get the authorities all
* the time for nothing (for performance reasons). This is the #1 goal: we should not impact our users'
* application because of this use-case.</li>
* <li> Not having an outer join causes n+1 requests to the database. This is not a real issue as
* we have by default a second-level cache. This means on the first HTTP call we do the n+1 requests,
* but then all authorities come from the cache, so in fact it's much better than doing an outer join
* (which will get lots of data from the database, for each HTTP call).</li>
* <li> As this manages users, for security reasons, we'd rather have a DTO layer.</li>
* </ul>
* <p>
* Another option would be to have a specific JPA entity graph to handle this case.
*/
@RestController
@RequestMapping("/api")
public class UserResource {
private final Logger log = LoggerFactory.getLogger(UserResource.class);
private final UserService userService;
private final UserRepository userRepository;
private final MailService mailService;
public UserResource(UserService userService, UserRepository userRepository, MailService mailService) {
this.userService = userService;
this.userRepository = userRepository;
this.mailService = mailService;
}
/**
* POST /users : Creates a new user.
* <p>
* Creates a new user if the login and email are not already used, and sends an
* mail with an activation link.
* The user needs to be activated on creation.
*
* @param userDTO the user to create
* @return the ResponseEntity with status 201 (Created) and with body the new user, or with status 400 (Bad Request) if the login or email is already in use
* @throws URISyntaxException if the Location URI syntax is incorrect
* @throws BadRequestAlertException 400 (Bad Request) if the login or email is already in use
*/
@PostMapping("/users")
@PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")")
public ResponseEntity<User> createUser(@Valid @RequestBody UserDTO userDTO) throws URISyntaxException {
log.debug("REST request to save User : {}", userDTO);
if (userDTO.getId() != null) {
throw new BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists");
// Lowercase the user login before comparing with database
} else if (userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).isPresent()) {
throw new LoginAlreadyUsedException();
} else if (userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).isPresent()) {
throw new EmailAlreadyUsedException();
} else {
User newUser = userService.createUser(userDTO);
mailService.sendCreationEmail(newUser);
return ResponseEntity.created(new URI("/api/users/" + newUser.getLogin()))
.headers(HeaderUtil.createAlert( "userManagement.created", newUser.getLogin()))
.body(newUser);
}
}
/**
* PUT /users : Updates an existing User.
*
* @param userDTO the user to update
* @return the ResponseEntity with status 200 (OK) and with body the updated user
* @throws EmailAlreadyUsedException 400 (Bad Request) if the email is already in use
* @throws LoginAlreadyUsedException 400 (Bad Request) if the login is already in use
*/
@PutMapping("/users")
@PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")")
public ResponseEntity<UserDTO> updateUser(@Valid @RequestBody UserDTO userDTO) {
log.debug("REST request to update User : {}", userDTO);
Optional<User> existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail());
if (existingUser.isPresent() && (!existingUser.get().getId().equals(userDTO.getId()))) {
throw new EmailAlreadyUsedException();
}
existingUser = userRepository.findOneByLogin(userDTO.getLogin().toLowerCase());
if (existingUser.isPresent() && (!existingUser.get().getId().equals(userDTO.getId()))) {
throw new LoginAlreadyUsedException();
}
Optional<UserDTO> updatedUser = userService.updateUser(userDTO);
return ResponseUtil.wrapOrNotFound(updatedUser,
HeaderUtil.createAlert("userManagement.updated", userDTO.getLogin()));
}
/**
* GET /users : get all users.
*
* @param pageable the pagination information
* @return the ResponseEntity with status 200 (OK) and with body all users
*/
@GetMapping("/users")
public ResponseEntity<List<UserDTO>> getAllUsers(Pageable pageable) {
final Page<UserDTO> page = userService.getAllManagedUsers(pageable);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/users");
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
}
/**
* @return a string list of the all of the roles
*/
@GetMapping("/users/authorities")
@PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")")
public List<String> getAuthorities() {
return userService.getAuthorities();
}
/**
* GET /users/:login : get the "login" user.
*
* @param login the login of the user to find
* @return the ResponseEntity with status 200 (OK) and with body the "login" user, or with status 404 (Not Found)
*/
@GetMapping("/users/{login:" + Constants.LOGIN_REGEX + "}")
public ResponseEntity<UserDTO> getUser(@PathVariable String login) {
log.debug("REST request to get User : {}", login);
return ResponseUtil.wrapOrNotFound(
userService.getUserWithAuthoritiesByLogin(login)
.map(UserDTO::new));
}
/**
* DELETE /users/:login : delete the "login" User.
*
* @param login the login of the user to delete
* @return the ResponseEntity with status 200 (OK)
*/
@DeleteMapping("/users/{login:" + Constants.LOGIN_REGEX + "}")
@PreAuthorize("hasRole(\"" + AuthoritiesConstants.ADMIN + "\")")
public ResponseEntity<Void> deleteUser(@PathVariable String login) {
log.debug("REST request to delete User: {}", login);
userService.deleteUser(login);
return ResponseEntity.ok().headers(HeaderUtil.createAlert( "userManagement.deleted", login)).build();
}
}

View File

@ -0,0 +1,42 @@
package org.hostsharing.hsadminng.web.rest.errors;
import org.zalando.problem.AbstractThrowableProblem;
import org.zalando.problem.Status;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
public class BadRequestAlertException extends AbstractThrowableProblem {
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(type, defaultMessage, Status.BAD_REQUEST, null, null, null, getAlertParameters(entityName, errorKey));
this.entityName = entityName;
this.errorKey = errorKey;
}
public String getEntityName() {
return entityName;
}
public String getErrorKey() {
return errorKey;
}
private static Map<String, Object> getAlertParameters(String entityName, String errorKey) {
Map<String, Object> parameters = new HashMap<>();
parameters.put("message", "error." + errorKey);
parameters.put("params", entityName);
return parameters;
}
}

View File

@ -0,0 +1,54 @@
package org.hostsharing.hsadminng.web.rest.errors;
import org.zalando.problem.AbstractThrowableProblem;
import java.util.HashMap;
import java.util.Map;
import static org.zalando.problem.Status.BAD_REQUEST;
/**
* Custom, parameterized exception, which can be translated on the client side.
* For example:
*
* <pre>
* throw new CustomParameterizedException(&quot;myCustomError&quot;, &quot;hello&quot;, &quot;world&quot;);
* </pre>
*
* Can be translated with:
*
* <pre>
* "error.myCustomError" : "The server says {{param0}} to {{param1}}"
* </pre>
*/
public class CustomParameterizedException extends AbstractThrowableProblem {
private static final long serialVersionUID = 1L;
private static final String PARAM = "param";
public CustomParameterizedException(String message, String... params) {
this(message, toParamMap(params));
}
public CustomParameterizedException(String message, Map<String, Object> paramMap) {
super(ErrorConstants.PARAMETERIZED_TYPE, "Parameterized Exception", BAD_REQUEST, null, null, null, toProblemParameters(message, paramMap));
}
public static Map<String, Object> toParamMap(String... params) {
Map<String, Object> paramMap = new HashMap<>();
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
paramMap.put(PARAM + i, params[i]);
}
}
return paramMap;
}
public static Map<String, Object> toProblemParameters(String message, Map<String, Object> paramMap) {
Map<String, Object> parameters = new HashMap<>();
parameters.put("message", message);
parameters.put("params", paramMap);
return parameters;
}
}

View File

@ -0,0 +1,10 @@
package org.hostsharing.hsadminng.web.rest.errors;
public class EmailAlreadyUsedException extends BadRequestAlertException {
private static final long serialVersionUID = 1L;
public EmailAlreadyUsedException() {
super(ErrorConstants.EMAIL_ALREADY_USED_TYPE, "Email is already in use!", "userManagement", "emailexists");
}
}

View File

@ -0,0 +1,13 @@
package org.hostsharing.hsadminng.web.rest.errors;
import org.zalando.problem.AbstractThrowableProblem;
import org.zalando.problem.Status;
public class EmailNotFoundException extends AbstractThrowableProblem {
private static final long serialVersionUID = 1L;
public EmailNotFoundException() {
super(ErrorConstants.EMAIL_NOT_FOUND_TYPE, "Email address not registered", Status.BAD_REQUEST);
}
}

View File

@ -0,0 +1,21 @@
package org.hostsharing.hsadminng.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");
public static final URI PARAMETERIZED_TYPE = URI.create(PROBLEM_BASE_URL + "/parameterized");
public static final URI ENTITY_NOT_FOUND_TYPE = URI.create(PROBLEM_BASE_URL + "/entity-not-found");
public static final URI INVALID_PASSWORD_TYPE = URI.create(PROBLEM_BASE_URL + "/invalid-password");
public static final URI EMAIL_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/email-already-used");
public static final URI LOGIN_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/login-already-used");
public static final URI EMAIL_NOT_FOUND_TYPE = URI.create(PROBLEM_BASE_URL + "/email-not-found");
private ErrorConstants() {
}
}

View File

@ -0,0 +1,112 @@
package org.hostsharing.hsadminng.web.rest.errors;
import org.hostsharing.hsadminng.web.rest.util.HeaderUtil;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.DefaultProblem;
import org.zalando.problem.Problem;
import org.zalando.problem.ProblemBuilder;
import org.zalando.problem.Status;
import org.zalando.problem.spring.web.advice.ProblemHandling;
import org.zalando.problem.violations.ConstraintViolationProblem;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
/**
* 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 implements ProblemHandling {
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 String VIOLATIONS_KEY = "violations";
/**
* Post-process the Problem payload to add the message key for the front-end if needed
*/
@Override
public ResponseEntity<Problem> process(@Nullable ResponseEntity<Problem> entity, NativeWebRequest request) {
if (entity == null) {
return entity;
}
Problem problem = entity.getBody();
if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) {
return entity;
}
ProblemBuilder builder = Problem.builder()
.withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType())
.withStatus(problem.getStatus())
.withTitle(problem.getTitle())
.with(PATH_KEY, request.getNativeRequest(HttpServletRequest.class).getRequestURI());
if (problem instanceof ConstraintViolationProblem) {
builder
.with(VIOLATIONS_KEY, ((ConstraintViolationProblem) problem).getViolations())
.with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION);
} else {
builder
.withCause(((DefaultProblem) problem).getCause())
.withDetail(problem.getDetail())
.withInstance(problem.getInstance());
problem.getParameters().forEach(builder::with);
if (!problem.getParameters().containsKey(MESSAGE_KEY) && problem.getStatus() != null) {
builder.with(MESSAGE_KEY, "error.http." + problem.getStatus().getStatusCode());
}
}
return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode());
}
@Override
public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) {
BindingResult result = ex.getBindingResult();
List<FieldErrorVM> fieldErrors = result.getFieldErrors().stream()
.map(f -> new FieldErrorVM(f.getObjectName(), f.getField(), f.getCode()))
.collect(Collectors.toList());
Problem problem = Problem.builder()
.withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
.withTitle("Method argument not valid")
.withStatus(defaultConstraintViolationStatus())
.with(MESSAGE_KEY, ErrorConstants.ERR_VALIDATION)
.with(FIELD_ERRORS_KEY, fieldErrors)
.build();
return create(ex, problem, request);
}
@ExceptionHandler
public ResponseEntity<Problem> handleNoSuchElementException(NoSuchElementException ex, NativeWebRequest request) {
Problem problem = Problem.builder()
.withStatus(Status.NOT_FOUND)
.with(MESSAGE_KEY, ErrorConstants.ENTITY_NOT_FOUND_TYPE)
.build();
return create(ex, problem, request);
}
@ExceptionHandler
public ResponseEntity<Problem> handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) {
return create(ex, request, HeaderUtil.createFailureAlert(ex.getEntityName(), ex.getErrorKey(), ex.getMessage()));
}
@ExceptionHandler
public ResponseEntity<Problem> handleConcurrencyFailure(ConcurrencyFailureException ex, NativeWebRequest request) {
Problem problem = Problem.builder()
.withStatus(Status.CONFLICT)
.with(MESSAGE_KEY, ErrorConstants.ERR_CONCURRENCY_FAILURE)
.build();
return create(ex, problem, request);
}
}

View File

@ -0,0 +1,33 @@
package org.hostsharing.hsadminng.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;
}
}

View File

@ -0,0 +1,16 @@
package org.hostsharing.hsadminng.web.rest.errors;
import org.zalando.problem.AbstractThrowableProblem;
import org.zalando.problem.Status;
/**
* Simple exception with a message, that returns an Internal Server Error code.
*/
public class InternalServerErrorException extends AbstractThrowableProblem {
private static final long serialVersionUID = 1L;
public InternalServerErrorException(String message) {
super(ErrorConstants.DEFAULT_TYPE, message, Status.INTERNAL_SERVER_ERROR);
}
}

View File

@ -0,0 +1,13 @@
package org.hostsharing.hsadminng.web.rest.errors;
import org.zalando.problem.AbstractThrowableProblem;
import org.zalando.problem.Status;
public class InvalidPasswordException extends AbstractThrowableProblem {
private static final long serialVersionUID = 1L;
public InvalidPasswordException() {
super(ErrorConstants.INVALID_PASSWORD_TYPE, "Incorrect password", Status.BAD_REQUEST);
}
}

View File

@ -0,0 +1,10 @@
package org.hostsharing.hsadminng.web.rest.errors;
public class LoginAlreadyUsedException extends BadRequestAlertException {
private static final long serialVersionUID = 1L;
public LoginAlreadyUsedException() {
super(ErrorConstants.LOGIN_ALREADY_USED_TYPE, "Login name already used!", "userManagement", "userexists");
}
}

View File

@ -0,0 +1,6 @@
/**
* Specific errors used with Zalando's "problem-spring-web" library.
*
* More information on https://github.com/zalando/problem-spring-web
*/
package org.hostsharing.hsadminng.web.rest.errors;

View File

@ -0,0 +1,4 @@
/**
* Spring MVC REST controllers.
*/
package org.hostsharing.hsadminng.web.rest;

View File

@ -0,0 +1,45 @@
package org.hostsharing.hsadminng.web.rest.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
/**
* Utility class for HTTP headers creation.
*/
public final class HeaderUtil {
private static final Logger log = LoggerFactory.getLogger(HeaderUtil.class);
private static final String APPLICATION_NAME = "hsadminNgApp";
private HeaderUtil() {
}
public static HttpHeaders createAlert(String message, String param) {
HttpHeaders headers = new HttpHeaders();
headers.add("X-" + APPLICATION_NAME + "-alert", message);
headers.add("X-" + APPLICATION_NAME + "-params", param);
return headers;
}
public static HttpHeaders createEntityCreationAlert(String entityName, String param) {
return createAlert(APPLICATION_NAME + "." + entityName + ".created", param);
}
public static HttpHeaders createEntityUpdateAlert(String entityName, String param) {
return createAlert(APPLICATION_NAME + "." + entityName + ".updated", param);
}
public static HttpHeaders createEntityDeletionAlert(String entityName, String param) {
return createAlert(APPLICATION_NAME + "." + entityName + ".deleted", param);
}
public static HttpHeaders createFailureAlert(String entityName, String errorKey, String defaultMessage) {
log.error("Entity processing failed, {}", defaultMessage);
HttpHeaders headers = new HttpHeaders();
headers.add("X-" + APPLICATION_NAME + "-error", "error." + errorKey);
headers.add("X-" + APPLICATION_NAME + "-params", entityName);
return headers;
}
}

View File

@ -0,0 +1,45 @@
package org.hostsharing.hsadminng.web.rest.util;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpHeaders;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Utility class for handling pagination.
*
* <p>
* Pagination uses the same principles as the <a href="https://developer.github.com/v3/#pagination">GitHub API</a>,
* and follow <a href="http://tools.ietf.org/html/rfc5988">RFC 5988 (Link header)</a>.
*/
public final class PaginationUtil {
private PaginationUtil() {
}
public static <T> HttpHeaders generatePaginationHttpHeaders(Page<T> page, String baseUrl) {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Total-Count", Long.toString(page.getTotalElements()));
String link = "";
if ((page.getNumber() + 1) < page.getTotalPages()) {
link = "<" + generateUri(baseUrl, page.getNumber() + 1, page.getSize()) + ">; rel=\"next\",";
}
// prev link
if ((page.getNumber()) > 0) {
link += "<" + generateUri(baseUrl, page.getNumber() - 1, page.getSize()) + ">; rel=\"prev\",";
}
// last and first link
int lastPage = 0;
if (page.getTotalPages() > 0) {
lastPage = page.getTotalPages() - 1;
}
link += "<" + generateUri(baseUrl, lastPage, page.getSize()) + ">; rel=\"last\",";
link += "<" + generateUri(baseUrl, 0, page.getSize()) + ">; rel=\"first\"";
headers.add(HttpHeaders.LINK, link);
return headers;
}
private static String generateUri(String baseUrl, int page, int size) {
return UriComponentsBuilder.fromUriString(baseUrl).queryParam("page", page).queryParam("size", size).toUriString();
}
}

View File

@ -0,0 +1,27 @@
package org.hostsharing.hsadminng.web.rest.vm;
/**
* View Model object for storing the user's key and password.
*/
public class KeyAndPasswordVM {
private String key;
private String newPassword;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}

View File

@ -0,0 +1,46 @@
package org.hostsharing.hsadminng.web.rest.vm;
import ch.qos.logback.classic.Logger;
/**
* View Model object for storing a Logback logger.
*/
public class LoggerVM {
private String name;
private String level;
public LoggerVM(Logger logger) {
this.name = logger.getName();
this.level = logger.getEffectiveLevel().toString();
}
public LoggerVM() {
// Empty public constructor used by Jackson.
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
@Override
public String toString() {
return "LoggerVM{" +
"name='" + name + '\'' +
", level='" + level + '\'' +
'}';
}
}

View File

@ -0,0 +1,52 @@
package org.hostsharing.hsadminng.web.rest.vm;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* View Model object for storing a user's credentials.
*/
public class LoginVM {
@NotNull
@Size(min = 1, max = 50)
private String username;
@NotNull
@Size(min = ManagedUserVM.PASSWORD_MIN_LENGTH, max = ManagedUserVM.PASSWORD_MAX_LENGTH)
private String password;
private Boolean rememberMe;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(Boolean rememberMe) {
this.rememberMe = rememberMe;
}
@Override
public String toString() {
return "LoginVM{" +
"username='" + username + '\'' +
", rememberMe=" + rememberMe +
'}';
}
}

View File

@ -0,0 +1,35 @@
package org.hostsharing.hsadminng.web.rest.vm;
import org.hostsharing.hsadminng.service.dto.UserDTO;
import javax.validation.constraints.Size;
/**
* View Model extending the UserDTO, which is meant to be used in the user management UI.
*/
public class ManagedUserVM extends UserDTO {
public static final int PASSWORD_MIN_LENGTH = 4;
public static final int PASSWORD_MAX_LENGTH = 100;
@Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH)
private String password;
public ManagedUserVM() {
// Empty constructor needed for Jackson.
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "ManagedUserVM{" +
"} " + super.toString();
}
}

View File

@ -0,0 +1,4 @@
/**
* View Models used by Spring MVC REST controllers.
*/
package org.hostsharing.hsadminng.web.rest.vm;