rename folders
This commit is contained in:
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
bin/
|
||||||
|
target/
|
||||||
|
.settings/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
hs_err*.log
|
||||||
|
application.properties
|
||||||
|
usernames.txt
|
||||||
|
lucene
|
||||||
|
|
||||||
|
.vscode
|
||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>de.champonthis</groupId>
|
||||||
|
<artifactId>buntspecht</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<maven.compiler.release></maven.compiler.release>
|
||||||
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
|
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||||
|
<querydsl.version>5.1.0</querydsl.version>
|
||||||
|
<revision>0.4.0</revision>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.3.4</version>
|
||||||
|
<relativePath />
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.session</groupId>
|
||||||
|
<artifactId>spring-session-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Query DSL -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.querydsl</groupId>
|
||||||
|
<artifactId>querydsl-apt</artifactId>
|
||||||
|
<version>${querydsl.version}</version>
|
||||||
|
<classifier>jakarta</classifier>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.querydsl</groupId>
|
||||||
|
<artifactId>querydsl-jpa</artifactId>
|
||||||
|
<version>${querydsl.version}</version>
|
||||||
|
<classifier>jakarta</classifier>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Utils -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-validator</groupId>
|
||||||
|
<artifactId>commons-validator</artifactId>
|
||||||
|
<version>1.9.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk18on</artifactId>
|
||||||
|
<version>1.78.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.passay</groupId>
|
||||||
|
<artifactId>passay</artifactId>
|
||||||
|
<version>1.6.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Datbase -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>de.champonthis.buntspecht.Application</mainClass>
|
||||||
|
<finalName>buntspecht</finalName>
|
||||||
|
<executable>true</executable>
|
||||||
|
<layout>ZIP</layout>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>build-info</id>
|
||||||
|
<goals>
|
||||||
|
<goal>build-info</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package de.champonthis.buntspecht;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||||
|
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class Application extends SpringBootServletInitializer {
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(Application.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package de.champonthis.buntspecht;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableJpaAuditing
|
||||||
|
public class JPAConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EntityManager em;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JPAQueryFactory jpaQueryFactory() {
|
||||||
|
return new JPAQueryFactory(em);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
package de.champonthis.buntspecht.businesslogic;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.model.SystemProperty;
|
||||||
|
import de.champonthis.buntspecht.repository.SystemPropertyRepository;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SystemPropertyManager {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemPropertyRepository systemPropertyRepository;
|
||||||
|
|
||||||
|
public boolean has(String key) {
|
||||||
|
return systemPropertyRepository.existsById(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String key) {
|
||||||
|
return systemPropertyRepository.findById(key).orElse(new SystemProperty()).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String key, String defaultValue) {
|
||||||
|
return systemPropertyRepository.findById(key).orElse(new SystemProperty(key, defaultValue)).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBoolean(String key) {
|
||||||
|
return getBoolean(key, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBoolean(String key, boolean defaultValue) {
|
||||||
|
return Boolean.valueOf(get(key, String.valueOf(defaultValue)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInteger(String key) {
|
||||||
|
return getInteger(key, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInteger(String key, int defaultValue) {
|
||||||
|
return Integer.valueOf(get(key, String.valueOf(defaultValue)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLong(String key) {
|
||||||
|
return getLong(key, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLong(String key, long defaultValue) {
|
||||||
|
return Long.valueOf(get(key, String.valueOf(defaultValue)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(String key, String value) {
|
||||||
|
Assert.isTrue(!systemPropertyRepository.existsById(key),
|
||||||
|
"System Property already exists, use update method to change value!");
|
||||||
|
systemPropertyRepository.save(new SystemProperty(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(String key, String value) {
|
||||||
|
Assert.isTrue(systemPropertyRepository.existsById(key),
|
||||||
|
"System Property does not exists, use add method to add new!");
|
||||||
|
SystemProperty systemProperty = systemPropertyRepository.findById(key).get();
|
||||||
|
systemProperty.setValue(value);
|
||||||
|
systemPropertyRepository.save(systemProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(String key, String value) {
|
||||||
|
if (systemPropertyRepository.existsById(key)) {
|
||||||
|
update(key, value);
|
||||||
|
} else {
|
||||||
|
add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
package de.champonthis.buntspecht.businesslogic;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.querydsl.core.BooleanBuilder;
|
||||||
|
import com.querydsl.core.QueryResults;
|
||||||
|
import com.querydsl.core.Tuple;
|
||||||
|
import com.querydsl.core.types.Order;
|
||||||
|
import com.querydsl.core.types.OrderSpecifier;
|
||||||
|
import com.querydsl.core.types.Path;
|
||||||
|
import com.querydsl.core.types.Predicate;
|
||||||
|
import com.querydsl.jpa.impl.JPAQuery;
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.controller.model.TurnoverFilterModel;
|
||||||
|
import de.champonthis.buntspecht.model.QTurnover;
|
||||||
|
import de.champonthis.buntspecht.model.Turnover;
|
||||||
|
import de.champonthis.buntspecht.repository.TurnoverRepository;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TurnoverManager {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TurnoverRepository turnoverRepository;
|
||||||
|
@Autowired
|
||||||
|
private JPAQueryFactory jpaQueryFactory;
|
||||||
|
|
||||||
|
private QTurnover qTurnover = QTurnover.turnover;
|
||||||
|
|
||||||
|
public QueryResults<Turnover> fetch(long limit, long offset, String sortBy, boolean descending,
|
||||||
|
TurnoverFilterModel filter) {
|
||||||
|
return fetch(null, limit, offset, sortBy, descending, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryResults<Turnover> fetch(String username, long limit, long offset, String sortBy, boolean descending,
|
||||||
|
TurnoverFilterModel filter) {
|
||||||
|
BooleanBuilder builder = new BooleanBuilder();
|
||||||
|
|
||||||
|
if (StringUtils.hasText(username)) {
|
||||||
|
builder.and(qTurnover.username.eq(username));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.and(buildFilter(filter));
|
||||||
|
|
||||||
|
JPAQuery<Turnover> query = jpaQueryFactory.from(qTurnover).where(builder.getValue()).select(qTurnover);
|
||||||
|
Long total = query.clone().select(qTurnover.id.countDistinct()).fetchOne();
|
||||||
|
|
||||||
|
if (StringUtils.hasText(sortBy)) {
|
||||||
|
Path<? extends Comparable<?>> path = null;
|
||||||
|
switch (sortBy) {
|
||||||
|
case "created":
|
||||||
|
path = qTurnover.created;
|
||||||
|
break;
|
||||||
|
case "dueDate":
|
||||||
|
path = qTurnover.dueDate;
|
||||||
|
break;
|
||||||
|
case "updated":
|
||||||
|
path = qTurnover.updated;
|
||||||
|
break;
|
||||||
|
case "customer":
|
||||||
|
path = qTurnover.customer;
|
||||||
|
break;
|
||||||
|
case "price":
|
||||||
|
path = qTurnover.price;
|
||||||
|
break;
|
||||||
|
case "timeInvestment":
|
||||||
|
path = qTurnover.timeInvestment;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (path != null) {
|
||||||
|
query.orderBy(new OrderSpecifier<>(descending ? Order.DESC : Order.ASC, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Turnover> result = query.limit(limit).offset(offset).fetch();
|
||||||
|
return new QueryResults<Turnover>(result, limit, offset, total == null ? 0L : total);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Predicate buildFilter(TurnoverFilterModel filter) {
|
||||||
|
BooleanBuilder builder = new BooleanBuilder();
|
||||||
|
|
||||||
|
if (filter != null) {
|
||||||
|
if (filter.getCreated() != null) {
|
||||||
|
if (filter.getCreated().getMin() != null) {
|
||||||
|
builder.and(qTurnover.created.after(filter.getCreated().getMin()));
|
||||||
|
}
|
||||||
|
if (filter.getCreated().getMax() != null) {
|
||||||
|
builder.and(qTurnover.created.before(filter.getCreated().getMax()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filter.getDueDate() != null) {
|
||||||
|
if (filter.getDueDate().getMin() != null) {
|
||||||
|
builder.and(qTurnover.dueDate.after(filter.getDueDate().getMin()));
|
||||||
|
}
|
||||||
|
if (filter.getDueDate().getMax() != null) {
|
||||||
|
builder.and(qTurnover.dueDate.before(filter.getDueDate().getMax()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filter.getUpdated() != null) {
|
||||||
|
if (filter.getUpdated().getMin() != null) {
|
||||||
|
builder.and(qTurnover.updated.after(filter.getUpdated().getMin()));
|
||||||
|
}
|
||||||
|
if (filter.getUpdated().getMax() != null) {
|
||||||
|
builder.and(qTurnover.updated.before(filter.getUpdated().getMax()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filter.getCustomer() != null) {
|
||||||
|
builder.and(qTurnover.customer.contains(filter.getCustomer()));
|
||||||
|
}
|
||||||
|
if (filter.getMotif() != null) {
|
||||||
|
builder.and(qTurnover.motif.contains(filter.getMotif()));
|
||||||
|
}
|
||||||
|
if (filter.getPrice() != null) {
|
||||||
|
if (filter.getPrice().getMin() != null) {
|
||||||
|
builder.and(qTurnover.price.goe(filter.getPrice().getMin()));
|
||||||
|
}
|
||||||
|
if (filter.getPrice().getMax() != null) {
|
||||||
|
builder.and(qTurnover.price.loe(filter.getPrice().getMax()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filter.getTimeInvestment() != null) {
|
||||||
|
if (filter.getTimeInvestment().getMin() != null) {
|
||||||
|
builder.and(qTurnover.timeInvestment.goe(filter.getTimeInvestment().getMin()));
|
||||||
|
}
|
||||||
|
if (filter.getTimeInvestment().getMax() != null) {
|
||||||
|
builder.and(qTurnover.timeInvestment.loe(filter.getTimeInvestment().getMax()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Turnover get(Long id) {
|
||||||
|
return turnoverRepository.findById(id).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Turnover save(Turnover turnover) {
|
||||||
|
return turnoverRepository.save(turnover);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean exists(Long id) {
|
||||||
|
return turnoverRepository.existsById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Turnover turnover) {
|
||||||
|
turnoverRepository.delete(turnover);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteById(Long id) {
|
||||||
|
turnoverRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteByUsername(String username) {
|
||||||
|
turnoverRepository.deleteAllInBatch(turnoverRepository.findAll(qTurnover.username.eq(username)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public QueryResults<Tuple> overview(String username, long limit, long offset, String sortBy, boolean descending,
|
||||||
|
TurnoverFilterModel filter) {
|
||||||
|
BooleanBuilder builder = new BooleanBuilder();
|
||||||
|
|
||||||
|
if (StringUtils.hasText(username)) {
|
||||||
|
builder.and(qTurnover.username.eq(username));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.and(buildFilter(filter));
|
||||||
|
|
||||||
|
JPAQuery<Tuple> query = jpaQueryFactory.from(qTurnover).where(builder.getValue()).groupBy(qTurnover.username)
|
||||||
|
.select(qTurnover.username.as("username"), qTurnover.price.sum().as("price"),
|
||||||
|
qTurnover.timeInvestment.sum().as("timeInvestment"));
|
||||||
|
Long total = query.clone().select(qTurnover.username.countDistinct()).fetchOne();
|
||||||
|
|
||||||
|
if (StringUtils.hasText(sortBy)) {
|
||||||
|
Path<? extends Comparable<?>> path = null;
|
||||||
|
switch (sortBy) {
|
||||||
|
case "username":
|
||||||
|
path = qTurnover.username;
|
||||||
|
break;
|
||||||
|
case "price":
|
||||||
|
path = qTurnover.price;
|
||||||
|
break;
|
||||||
|
case "timeInvestment":
|
||||||
|
path = qTurnover.timeInvestment;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (path != null) {
|
||||||
|
query.orderBy(new OrderSpecifier<>(descending ? Order.DESC : Order.ASC, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Tuple> result = query.limit(limit).offset(offset)
|
||||||
|
.fetch();
|
||||||
|
|
||||||
|
return new QueryResults<Tuple>(result, limit, offset, total == null ? 0L : total);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
package de.champonthis.buntspecht.businesslogic;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
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.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.querydsl.core.BooleanBuilder;
|
||||||
|
import com.querydsl.core.QueryResults;
|
||||||
|
import com.querydsl.core.types.Order;
|
||||||
|
import com.querydsl.core.types.OrderSpecifier;
|
||||||
|
import com.querydsl.core.types.Path;
|
||||||
|
import com.querydsl.jpa.impl.JPAQuery;
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.model.QUser;
|
||||||
|
import de.champonthis.buntspecht.model.User;
|
||||||
|
import de.champonthis.buntspecht.repository.UserRepository;
|
||||||
|
import de.champonthis.buntspecht.security.LocalUserDetails;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserManager implements UserDetailsService, SmartInitializingSingleton {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(UserManager.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
@Autowired
|
||||||
|
private JPAQueryFactory jpaQueryFactory;
|
||||||
|
@Autowired
|
||||||
|
private TurnoverManager turnoverManager;
|
||||||
|
private QUser qUser = QUser.user;
|
||||||
|
|
||||||
|
@Value("${admin.password:}")
|
||||||
|
private String adminPassword;
|
||||||
|
|
||||||
|
public QueryResults<User> fetch(long limit, long offset, String sortBy, boolean descending, String usernameFilter) {
|
||||||
|
BooleanBuilder builder = new BooleanBuilder();
|
||||||
|
|
||||||
|
if (StringUtils.hasText(usernameFilter)) {
|
||||||
|
builder.and(qUser.username.contains(usernameFilter));
|
||||||
|
}
|
||||||
|
|
||||||
|
JPAQuery<User> query = jpaQueryFactory.from(qUser).where(builder.getValue()).select(qUser);
|
||||||
|
Long total = query.clone().select(qUser.username.countDistinct()).fetchOne();
|
||||||
|
|
||||||
|
if (StringUtils.hasText(sortBy)) {
|
||||||
|
Path<? extends Comparable<?>> path = null;
|
||||||
|
switch (sortBy) {
|
||||||
|
case "username":
|
||||||
|
path = qUser.username;
|
||||||
|
break;
|
||||||
|
case "name":
|
||||||
|
path = qUser.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (path != null) {
|
||||||
|
query.orderBy(new OrderSpecifier<>(descending ? Order.DESC : Order.ASC, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<User> result = query.limit(limit).offset(offset).fetch();
|
||||||
|
return new QueryResults<User>(result, limit, offset, total == null ? 0L : total);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
User user = getByUsername(username);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
throw new UsernameNotFoundException(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GrantedAuthority> authorities = new ArrayList<>();
|
||||||
|
if (user.getRoles() != null) {
|
||||||
|
for (String role : user.getRoles()) {
|
||||||
|
authorities.add(new SimpleGrantedAuthority(role));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String passwordHash = user.getPasswordHash();
|
||||||
|
|
||||||
|
if (passwordHash == null) {
|
||||||
|
passwordHash = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalUserDetails userDetails = new LocalUserDetails(username, passwordHash, authorities);
|
||||||
|
return userDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterSingletonsInstantiated() {
|
||||||
|
if (!userRepository.exists(qUser.roles.contains("ROLE_ADMIN"))) {
|
||||||
|
if (!StringUtils.hasText(adminPassword)) {
|
||||||
|
adminPassword = RandomStringUtils.random(24, true, true);
|
||||||
|
logger.error("password for 'admin': " + adminPassword);
|
||||||
|
}
|
||||||
|
User admin = new User();
|
||||||
|
admin.setUsername("admin");
|
||||||
|
admin.setRoles(List.of("ROLE_ADMIN", "ROLE_DEBUG"));
|
||||||
|
admin.setPasswordHash(passwordEncoder.encode(adminPassword));
|
||||||
|
admin.setLocale("de");
|
||||||
|
userRepository.save(admin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public User getByUsername(String username) {
|
||||||
|
return userRepository.findOne(qUser.username.equalsIgnoreCase(username)).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User getByExternalId(String externalId) {
|
||||||
|
return userRepository.findOne(qUser.externalId.eq(externalId)).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User getByAuth(Authentication authentication) {
|
||||||
|
if (authentication != null) {
|
||||||
|
if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
||||||
|
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
|
||||||
|
return getByUsername(token.getName());
|
||||||
|
} else if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
|
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
|
||||||
|
String externalId = token.getAuthorizedClientRegistrationId() + "-" +
|
||||||
|
token.getName();
|
||||||
|
User user = getByExternalId(externalId);
|
||||||
|
if (user == null) {
|
||||||
|
user = new User();
|
||||||
|
user.setExternalId(externalId);
|
||||||
|
String tmpUsername = token.getPrincipal().getAttribute("preferred_username");
|
||||||
|
if (!StringUtils.hasText(tmpUsername)) {
|
||||||
|
tmpUsername = token.getPrincipal().getAttribute("username");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(tmpUsername)) {
|
||||||
|
tmpUsername = token.getPrincipal().getAttribute("name");
|
||||||
|
} else {
|
||||||
|
user.setName(token.getPrincipal().getAttribute("name"));
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(tmpUsername)) {
|
||||||
|
tmpUsername = token.getName();
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(tmpUsername)) {
|
||||||
|
tmpUsername = "user";
|
||||||
|
}
|
||||||
|
int count = 1;
|
||||||
|
String username = tmpUsername;
|
||||||
|
while (userRepository.exists(qUser.username.equalsIgnoreCase(username))) {
|
||||||
|
username = tmpUsername + "-" + count;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setUsername(username);
|
||||||
|
user.setEmail(token.getPrincipal().getAttribute("email"));
|
||||||
|
user = userRepository.save(user);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User save(User user) {
|
||||||
|
if (exists(user.getUsername())) {
|
||||||
|
user.setPasswordHash(this.getPasswordHash(user.getUsername()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean exists(String username) {
|
||||||
|
return userRepository.exists(qUser.username.equalsIgnoreCase(username));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(User user) {
|
||||||
|
turnoverManager.deleteByUsername(user.getUsername());
|
||||||
|
userRepository.delete(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public String getPasswordHash(String username) {
|
||||||
|
Assert.isTrue(userRepository.existsById(username), "User with username '" + username + "' not exists!");
|
||||||
|
return userRepository.findById(username).get().getPasswordHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
public User setPassword(String username, String password) {
|
||||||
|
Assert.isTrue(userRepository.existsById(username), "User with username '" + username + "' not exists!");
|
||||||
|
User user = userRepository.findById(username).get();
|
||||||
|
user.setPasswordHash(passwordEncoder.encode(password));
|
||||||
|
return userRepository.save(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
package de.champonthis.buntspecht.controller;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.controller.support.EntityResponseStatusException;
|
||||||
|
import de.champonthis.buntspecht.security.LocalUserDetails;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/auth")
|
||||||
|
public class AuthenticationController extends BaseController {
|
||||||
|
|
||||||
|
private static String authorizationRequestBaseUri = "oauth2/authorization";
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private ClientRegistrationRepository clientRegistrationRepository;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public Object me() {
|
||||||
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
if (auth != null && auth.getPrincipal() instanceof LocalUserDetails) {
|
||||||
|
return (LocalUserDetails) auth.getPrincipal();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@GetMapping("external")
|
||||||
|
public List<Client> getExternalLoginUrls() {
|
||||||
|
List<Client> clients = new ArrayList<>();
|
||||||
|
if (clientRegistrationRepository != null) {
|
||||||
|
Iterable<ClientRegistration> clientRegistrations = null;
|
||||||
|
ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository).as(Iterable.class);
|
||||||
|
if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
|
||||||
|
clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;
|
||||||
|
clientRegistrations.forEach(registration -> clients.add(new Client(registration.getRegistrationId(),
|
||||||
|
authorizationRequestBaseUri + "/" + registration.getRegistrationId())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Client {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private String loginUrl;
|
||||||
|
|
||||||
|
public Client(String id, String loginUrl) {
|
||||||
|
super();
|
||||||
|
this.id = id;
|
||||||
|
this.loginUrl = loginUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLoginUrl() {
|
||||||
|
return loginUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginUrl(String loginUrl) {
|
||||||
|
this.loginUrl = loginUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package de.champonthis.buntspecht.controller;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.security.LocalUserDetails;
|
||||||
|
|
||||||
|
public class BaseController {
|
||||||
|
|
||||||
|
protected boolean authenticated() {
|
||||||
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
return auth != null && auth.isAuthenticated();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getCurrentUsername() {
|
||||||
|
LocalUserDetails userDetails = getLocalUserDetails();
|
||||||
|
return userDetails != null ? userDetails.getUsername() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean hasRole(String role) {
|
||||||
|
LocalUserDetails userDetails = getLocalUserDetails();
|
||||||
|
return userDetails != null ? userDetails.getAuthorities().contains(new SimpleGrantedAuthority(role)) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LocalUserDetails getLocalUserDetails() {
|
||||||
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
return (auth != null && auth.getPrincipal() instanceof LocalUserDetails)
|
||||||
|
? (LocalUserDetails) auth.getPrincipal()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package de.champonthis.buntspecht.controller;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.validation.Errors;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PatchMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.querydsl.core.QueryResults;
|
||||||
|
import com.querydsl.core.Tuple;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.businesslogic.TurnoverManager;
|
||||||
|
import de.champonthis.buntspecht.businesslogic.UserManager;
|
||||||
|
import de.champonthis.buntspecht.controller.model.TurnoverFilterModel;
|
||||||
|
import de.champonthis.buntspecht.controller.model.TurnoverFilterModel.MinMax;
|
||||||
|
import de.champonthis.buntspecht.controller.support.EntityResponseStatusException;
|
||||||
|
import de.champonthis.buntspecht.controller.support.RequestBodyErrors;
|
||||||
|
import de.champonthis.buntspecht.controller.validation.TurnoverValidator;
|
||||||
|
import de.champonthis.buntspecht.model.Turnover;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/turnovers")
|
||||||
|
public class TurnoverController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TurnoverManager turnoverManager;
|
||||||
|
@Autowired
|
||||||
|
private UserManager userManager;
|
||||||
|
@Autowired
|
||||||
|
private TurnoverValidator turnoverValidator;
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping
|
||||||
|
@Transactional
|
||||||
|
public QueryResults<Turnover> fetch(
|
||||||
|
@RequestParam("limit") Optional<Long> limitParameter,
|
||||||
|
@RequestParam("offset") Optional<Long> offsetParameter,
|
||||||
|
@RequestParam("sort") Optional<String> sort,
|
||||||
|
@RequestParam("descending") Optional<Boolean> descending,
|
||||||
|
@RequestParam("from") Optional<Instant> from,
|
||||||
|
@RequestParam("to") Optional<Instant> to,
|
||||||
|
@RequestParam("created_from") Optional<Instant> fromCreated,
|
||||||
|
@RequestParam("created_to") Optional<Instant> toCreated,
|
||||||
|
@RequestParam("customer") Optional<String> customer,
|
||||||
|
@RequestParam("motif") Optional<String> motif) {
|
||||||
|
|
||||||
|
TurnoverFilterModel filter = new TurnoverFilterModel();
|
||||||
|
filter.setDueDate(new MinMax<Instant>(from.orElse(null), to.orElse(null)));
|
||||||
|
filter.setCreated(new MinMax<Instant>(fromCreated.orElse(null), toCreated.orElse(null)));
|
||||||
|
filter.setCustomer(customer.orElse(null));
|
||||||
|
filter.setMotif(motif.orElse(null));
|
||||||
|
|
||||||
|
return turnoverManager.fetch(getCurrentUsername(), limitParameter.orElse(15L), offsetParameter.orElse(0L),
|
||||||
|
sort.orElse("dueDate"), descending.orElse(false), filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping("/overview")
|
||||||
|
@Transactional
|
||||||
|
public Tuple overview(
|
||||||
|
@RequestParam("limit") Optional<Long> limitParameter,
|
||||||
|
@RequestParam("offset") Optional<Long> offsetParameter,
|
||||||
|
@RequestParam("sort") Optional<String> sort,
|
||||||
|
@RequestParam("descending") Optional<Boolean> descending,
|
||||||
|
@RequestParam("from") Optional<Instant> from,
|
||||||
|
@RequestParam("to") Optional<Instant> to,
|
||||||
|
@RequestParam("created_from") Optional<Instant> fromCreated,
|
||||||
|
@RequestParam("created_to") Optional<Instant> toCreated,
|
||||||
|
@RequestParam("customer") Optional<String> customer,
|
||||||
|
@RequestParam("motif") Optional<String> motif) {
|
||||||
|
|
||||||
|
TurnoverFilterModel filter = new TurnoverFilterModel();
|
||||||
|
filter.setDueDate(new MinMax<Instant>(from.orElse(null), to.orElse(null)));
|
||||||
|
filter.setCreated(new MinMax<Instant>(fromCreated.orElse(null), toCreated.orElse(null)));
|
||||||
|
filter.setCustomer(customer.orElse(null));
|
||||||
|
filter.setMotif(motif.orElse(null));
|
||||||
|
|
||||||
|
List<Tuple> result = turnoverManager.overview(getCurrentUsername(), limitParameter.orElse(15L),
|
||||||
|
offsetParameter.orElse(0L), sort.orElse("username"),
|
||||||
|
descending.orElse(false), filter).getResults();
|
||||||
|
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@Transactional
|
||||||
|
public Turnover get(@PathVariable("id") Long id) {
|
||||||
|
Turnover turnover = turnoverManager.get(id);
|
||||||
|
|
||||||
|
if (turnover == null || !getCurrentUsername().equals(turnover.getUsername())) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return turnover;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@PostMapping
|
||||||
|
@Transactional
|
||||||
|
public Turnover create(@RequestBody Turnover turnover) {
|
||||||
|
Errors errors = new RequestBodyErrors(turnover);
|
||||||
|
turnoverValidator.validate(turnover, errors);
|
||||||
|
|
||||||
|
if (errors.hasErrors()) {
|
||||||
|
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasRole("ROLE_ADMIN") || !StringUtils.hasText(turnover.getUsername())
|
||||||
|
|| !userManager.exists(turnover.getUsername())) {
|
||||||
|
turnover.setUsername(getCurrentUsername());
|
||||||
|
}
|
||||||
|
turnover.setCreated(Instant.now());
|
||||||
|
turnover.setUpdated(turnover.getCreated());
|
||||||
|
if (turnover.getDueDate() == null) {
|
||||||
|
turnover.setDueDate(turnover.getCreated());
|
||||||
|
}
|
||||||
|
|
||||||
|
return turnoverManager.save(turnover);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@PatchMapping
|
||||||
|
@Transactional
|
||||||
|
public Turnover update(@RequestBody Turnover turnover) {
|
||||||
|
Errors errors = new RequestBodyErrors(turnover);
|
||||||
|
turnoverValidator.validate(turnover, errors);
|
||||||
|
|
||||||
|
if (errors.hasErrors() || turnover.getId() == null || turnover.getId() == 0L
|
||||||
|
|| !turnoverManager.exists(turnover.getId())) {
|
||||||
|
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasRole("ROLE_ADMIN")) {
|
||||||
|
Turnover existing = turnoverManager.get(turnover.getId());
|
||||||
|
if (!getCurrentUsername().equals(existing.getUsername())) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
turnover.setUsername(getCurrentUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
Turnover existing = turnoverManager.get(turnover.getId());
|
||||||
|
|
||||||
|
if (existing.equals(turnover)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turnover.getDueDate() == null) {
|
||||||
|
turnover.setDueDate(turnover.getCreated());
|
||||||
|
}
|
||||||
|
|
||||||
|
turnover.setUpdated(Instant.now());
|
||||||
|
|
||||||
|
return turnoverManager.save(turnover);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package de.champonthis.buntspecht.controller;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.validation.Errors;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PatchMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.businesslogic.UserManager;
|
||||||
|
import de.champonthis.buntspecht.controller.model.UserPasswordModel;
|
||||||
|
import de.champonthis.buntspecht.controller.support.EntityResponseStatusException;
|
||||||
|
import de.champonthis.buntspecht.controller.support.RequestBodyErrors;
|
||||||
|
import de.champonthis.buntspecht.controller.validation.PasswordModelValidator;
|
||||||
|
import de.champonthis.buntspecht.model.User;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/users")
|
||||||
|
public class UserController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserManager userManager;
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
@Autowired
|
||||||
|
private PasswordModelValidator passwordModelValidator;
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping("/user")
|
||||||
|
@Transactional
|
||||||
|
public User get() {
|
||||||
|
return userManager.getByUsername(getCurrentUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@PatchMapping("/user")
|
||||||
|
@Transactional
|
||||||
|
public User updateUser(@RequestBody User user) {
|
||||||
|
if (!getCurrentUsername().equals(user.getUsername())) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
User orgUser = userManager.getByUsername(user.getUsername());
|
||||||
|
|
||||||
|
orgUser.setName(user.getName());
|
||||||
|
orgUser.setAbout(user.getAbout());
|
||||||
|
orgUser.setDarkTheme(user.isDarkTheme());
|
||||||
|
orgUser.setEmail(user.getEmail());
|
||||||
|
orgUser.setLocale(user.getLocale());
|
||||||
|
|
||||||
|
user = userManager.save(orgUser);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@PostMapping("/password")
|
||||||
|
public void changePassword(@RequestBody UserPasswordModel passwordModel) {
|
||||||
|
|
||||||
|
Errors errors = new RequestBodyErrors(passwordModel);
|
||||||
|
|
||||||
|
User user = userManager.getByUsername(getCurrentUsername());
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(passwordModel.getOld())
|
||||||
|
|| !passwordEncoder.matches(passwordModel.getOld(), userManager.getPasswordHash(user.getUsername()))) {
|
||||||
|
errors.rejectValue("old", "UNAUTHORIZED");
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordModelValidator.validate(passwordModel, errors);
|
||||||
|
|
||||||
|
if (errors.hasErrors()) {
|
||||||
|
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
userManager.setPassword(user.getUsername(), passwordModel.getPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.admin;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.SplittableRandom;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.model.QUser;
|
||||||
|
import de.champonthis.buntspecht.model.Turnover;
|
||||||
|
import de.champonthis.buntspecht.model.User;
|
||||||
|
import de.champonthis.buntspecht.repository.TurnoverRepository;
|
||||||
|
import de.champonthis.buntspecht.repository.UserRepository;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/debug")
|
||||||
|
public class DebugController {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(DebugController.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
@Autowired
|
||||||
|
private TurnoverRepository turnoverRepository;
|
||||||
|
|
||||||
|
SplittableRandom splittableRandom = new SplittableRandom();
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_DEBUG')")
|
||||||
|
@GetMapping("/random")
|
||||||
|
public void random(
|
||||||
|
@RequestParam("users") Optional<Integer> usersParameter,
|
||||||
|
@RequestParam("minEntries") Optional<Integer> minEntriesParameter,
|
||||||
|
@RequestParam("maxEntries") Optional<Integer> maxEntriesParameter,
|
||||||
|
@RequestParam("days") Optional<Integer> daysParameter) {
|
||||||
|
logger.warn("start random generation");
|
||||||
|
|
||||||
|
long userCount = userRepository.count(QUser.user.username.startsWith("Tätowier"));
|
||||||
|
|
||||||
|
List<String> newUser = new ArrayList<>();
|
||||||
|
|
||||||
|
for (long i = userCount + 1; i <= userCount + usersParameter.orElse(5); i++) {
|
||||||
|
User user = new User();
|
||||||
|
String username = (splittableRandom.nextBoolean() ? "Tätowiererin " : "Tätowierer ") + i;
|
||||||
|
String name = "Random " + RandomStringUtils.randomAlphanumeric(splittableRandom.nextInt(4, 8));
|
||||||
|
user.setUsername(username);
|
||||||
|
user.setName(name);
|
||||||
|
user.setPasswordHash(passwordEncoder.encode(username));
|
||||||
|
user = userRepository.save(user);
|
||||||
|
logger.trace("Created user: '" + username + "'");
|
||||||
|
newUser.add(user.getUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Created " + usersParameter.orElse(5) + " users");
|
||||||
|
|
||||||
|
Instant startInclusive = Instant.now().minus(daysParameter.orElse(350), ChronoUnit.DAYS);
|
||||||
|
Instant endExclusive = Instant.now();
|
||||||
|
|
||||||
|
for (String username : newUser) {
|
||||||
|
long numEntries = splittableRandom.nextLong(minEntriesParameter.orElse(3), maxEntriesParameter.orElse(20));
|
||||||
|
for (int i = 0; i < numEntries; i++) {
|
||||||
|
Turnover turnover = new Turnover();
|
||||||
|
turnover.setUsername(username);
|
||||||
|
turnover.setCreated(randomDate(startInclusive, endExclusive));
|
||||||
|
turnover.setUpdated(splittableRandom.nextBoolean() ? turnover.getCreated()
|
||||||
|
: randomDate(turnover.getCreated(), endExclusive));
|
||||||
|
turnover.setDueDate(splittableRandom.nextBoolean() ? turnover.getCreated()
|
||||||
|
: randomDate(startInclusive, turnover.getCreated()));
|
||||||
|
turnover.setCustomer(RandomStringUtils.randomAlphabetic(splittableRandom.nextInt(3, 10)));
|
||||||
|
|
||||||
|
turnover.setMotif(RandomStringUtils.randomAlphabetic(splittableRandom.nextInt(5, 30)));
|
||||||
|
|
||||||
|
turnover.setPrice(Float.valueOf(String.format("%.2f", splittableRandom.nextFloat(0.01f, 2000f))));
|
||||||
|
|
||||||
|
if (splittableRandom.nextBoolean()) {
|
||||||
|
turnover.setTimeInvestment(
|
||||||
|
Float.valueOf(String.format("%.2f", splittableRandom.nextFloat(0.01f, 20f))));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (splittableRandom.nextBoolean()) {
|
||||||
|
turnover.setRemark(RandomStringUtils.randomAlphabetic(splittableRandom.nextInt(10, 50)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (splittableRandom.nextInt(5) < 3) {
|
||||||
|
turnover.setMaterialConsumption(
|
||||||
|
RandomStringUtils.randomAlphabetic(splittableRandom.nextInt(10, 250)));
|
||||||
|
}
|
||||||
|
|
||||||
|
turnover = turnoverRepository.save(turnover);
|
||||||
|
logger.trace("Created turnover: '" + turnover.getId() + "'");
|
||||||
|
}
|
||||||
|
logger.info("Created " + numEntries + " turnovers of '" + username + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn("finished random generation");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Instant randomDate(Instant startInclusive, Instant endExclusive) {
|
||||||
|
long startSeconds = startInclusive.getEpochSecond();
|
||||||
|
long endSeconds = endExclusive.getEpochSecond();
|
||||||
|
long random = splittableRandom.nextLong(startSeconds, endSeconds);
|
||||||
|
|
||||||
|
return Instant.ofEpochSecond(random);
|
||||||
|
}
|
||||||
|
}
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.admin;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.controller.BaseController;
|
||||||
|
import de.champonthis.buntspecht.controller.support.EntityResponseStatusException;
|
||||||
|
import de.champonthis.buntspecht.model.SystemProperty;
|
||||||
|
import de.champonthis.buntspecht.repository.SystemPropertyRepository;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/properties")
|
||||||
|
public class SystemPropertiesController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemPropertyRepository systemPropertyRepository;
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@GetMapping()
|
||||||
|
public List<SystemProperty> getProperties(@RequestParam("page") Optional<Integer> pageParameter,
|
||||||
|
@RequestParam("size") Optional<Integer> sizeParameter) {
|
||||||
|
Sort sort = Sort.by("key").ascending();
|
||||||
|
return systemPropertyRepository.findAll(PageRequest.of(pageParameter.orElse(0), sizeParameter.orElse(10), sort))
|
||||||
|
.getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@GetMapping("/{key}")
|
||||||
|
public SystemProperty getProperty(@PathVariable("key") String key) {
|
||||||
|
if (!systemPropertyRepository.existsById(key)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return systemPropertyRepository.findById(key).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PostMapping("")
|
||||||
|
public SystemProperty createOrUpdate(@RequestBody SystemProperty systemProperty) {
|
||||||
|
return systemPropertyRepository.save(systemProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PostMapping("/list")
|
||||||
|
public List<SystemProperty> createOrUpdateList(@RequestBody List<SystemProperty> systemProperties) {
|
||||||
|
List<SystemProperty> result = new ArrayList<>();
|
||||||
|
for (SystemProperty systemProperty : systemProperties) {
|
||||||
|
result.add(
|
||||||
|
systemPropertyRepository.save(systemProperty));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@DeleteMapping("/{key}")
|
||||||
|
public void deleteProperty(@PathVariable("key") String key) {
|
||||||
|
if (!systemPropertyRepository.existsById(key)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
systemPropertyRepository.deleteById(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.admin;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.validation.Errors;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PatchMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.querydsl.core.QueryResults;
|
||||||
|
import com.querydsl.core.Tuple;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.businesslogic.TurnoverManager;
|
||||||
|
import de.champonthis.buntspecht.controller.BaseController;
|
||||||
|
import de.champonthis.buntspecht.controller.model.TurnoverFilterModel;
|
||||||
|
import de.champonthis.buntspecht.controller.model.TurnoverFilterModel.MinMax;
|
||||||
|
import de.champonthis.buntspecht.controller.support.EntityResponseStatusException;
|
||||||
|
import de.champonthis.buntspecht.controller.support.RequestBodyErrors;
|
||||||
|
import de.champonthis.buntspecht.controller.validation.TurnoverValidator;
|
||||||
|
import de.champonthis.buntspecht.model.Turnover;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/turnovers/manage")
|
||||||
|
public class TurnoverManagementController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TurnoverManager turnoverManager;
|
||||||
|
@Autowired
|
||||||
|
private TurnoverValidator turnoverValidator;
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@GetMapping
|
||||||
|
@Transactional
|
||||||
|
public QueryResults<Turnover> fetch(
|
||||||
|
@RequestParam("username") Optional<String> usernameParameter,
|
||||||
|
@RequestParam("limit") Optional<Long> limitParameter,
|
||||||
|
@RequestParam("offset") Optional<Long> offsetParameter,
|
||||||
|
@RequestParam("sort") Optional<String> sort,
|
||||||
|
@RequestParam("descending") Optional<Boolean> descending,
|
||||||
|
@RequestParam("from") Optional<Instant> from,
|
||||||
|
@RequestParam("to") Optional<Instant> to,
|
||||||
|
@RequestParam("created_from") Optional<Instant> fromCreated,
|
||||||
|
@RequestParam("created_to") Optional<Instant> toCreated,
|
||||||
|
@RequestParam("customer") Optional<String> customer,
|
||||||
|
@RequestParam("motif") Optional<String> motif) {
|
||||||
|
|
||||||
|
TurnoverFilterModel filter = new TurnoverFilterModel();
|
||||||
|
filter.setDueDate(new MinMax<Instant>(from.orElse(null), to.orElse(null)));
|
||||||
|
filter.setCreated(new MinMax<Instant>(fromCreated.orElse(null), toCreated.orElse(null)));
|
||||||
|
filter.setCustomer(customer.orElse(null));
|
||||||
|
filter.setMotif(motif.orElse(null));
|
||||||
|
|
||||||
|
return turnoverManager.fetch(usernameParameter.orElse(null), limitParameter.orElse(15L),
|
||||||
|
offsetParameter.orElse(0L), sort.orElse("dueDate"), descending.orElse(false), filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@GetMapping("/overview")
|
||||||
|
@Transactional
|
||||||
|
public QueryResults<Tuple> overview(
|
||||||
|
@RequestParam("username") Optional<String> usernameParameter,
|
||||||
|
@RequestParam("limit") Optional<Long> limitParameter,
|
||||||
|
@RequestParam("offset") Optional<Long> offsetParameter,
|
||||||
|
@RequestParam("sort") Optional<String> sort,
|
||||||
|
@RequestParam("descending") Optional<Boolean> descending,
|
||||||
|
@RequestParam("from") Optional<Instant> from,
|
||||||
|
@RequestParam("to") Optional<Instant> to,
|
||||||
|
@RequestParam("created_from") Optional<Instant> fromCreated,
|
||||||
|
@RequestParam("created_to") Optional<Instant> toCreated,
|
||||||
|
@RequestParam("customer") Optional<String> customer,
|
||||||
|
@RequestParam("motif") Optional<String> motif) {
|
||||||
|
|
||||||
|
TurnoverFilterModel filter = new TurnoverFilterModel();
|
||||||
|
filter.setDueDate(new MinMax<Instant>(from.orElse(null), to.orElse(null)));
|
||||||
|
filter.setCreated(new MinMax<Instant>(fromCreated.orElse(null), toCreated.orElse(null)));
|
||||||
|
filter.setCustomer(customer.orElse(null));
|
||||||
|
filter.setMotif(motif.orElse(null));
|
||||||
|
return turnoverManager.overview(usernameParameter.orElse(null), limitParameter.orElse(15L),
|
||||||
|
offsetParameter.orElse(0L), sort.orElse("username"),
|
||||||
|
descending.orElse(false), filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@Transactional
|
||||||
|
public Turnover getById(@PathVariable("id") Long id) {
|
||||||
|
if (!turnoverManager.exists(id)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return turnoverManager.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PatchMapping
|
||||||
|
@Transactional
|
||||||
|
public Turnover update(@RequestBody Turnover turnover) {
|
||||||
|
Errors errors = new RequestBodyErrors(turnover);
|
||||||
|
turnoverValidator.validate(turnover, errors);
|
||||||
|
|
||||||
|
if (errors.hasErrors()) {
|
||||||
|
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Turnover existing = turnoverManager.get(turnover.getId());
|
||||||
|
if (existing.equals(turnover)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turnover.getDueDate() == null) {
|
||||||
|
turnover.setDueDate(turnover.getCreated());
|
||||||
|
}
|
||||||
|
|
||||||
|
turnover.setUpdated(Instant.now());
|
||||||
|
|
||||||
|
return turnoverManager.save(turnover);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@Transactional
|
||||||
|
public void deleteById(@PathVariable("id") Long id) {
|
||||||
|
if (!turnoverManager.exists(id)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
turnoverManager.deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.admin;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.validation.Errors;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PatchMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.querydsl.core.QueryResults;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.businesslogic.UserManager;
|
||||||
|
import de.champonthis.buntspecht.controller.BaseController;
|
||||||
|
import de.champonthis.buntspecht.controller.admin.validation.UserValidator;
|
||||||
|
import de.champonthis.buntspecht.controller.model.UserPasswordModel;
|
||||||
|
import de.champonthis.buntspecht.controller.support.EntityResponseStatusException;
|
||||||
|
import de.champonthis.buntspecht.controller.support.RequestBodyErrors;
|
||||||
|
import de.champonthis.buntspecht.controller.validation.PasswordModelValidator;
|
||||||
|
import de.champonthis.buntspecht.model.User;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/users/manage")
|
||||||
|
public class UserManagementController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserManager userManager;
|
||||||
|
@Autowired
|
||||||
|
private UserValidator userValidator;
|
||||||
|
@Autowired
|
||||||
|
private PasswordModelValidator passwordModelValidator;
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@GetMapping
|
||||||
|
@Transactional
|
||||||
|
public QueryResults<User> fetch(
|
||||||
|
@RequestParam("limit") Optional<Long> limitParameter,
|
||||||
|
@RequestParam("offset") Optional<Long> offsetParameter,
|
||||||
|
@RequestParam("sort") Optional<String> sort,
|
||||||
|
@RequestParam("descending") Optional<Boolean> descending,
|
||||||
|
@RequestParam("filter") Optional<String> search) {
|
||||||
|
return userManager.fetch(limitParameter.orElse(15L), offsetParameter.orElse(0L), sort.orElse("username"),
|
||||||
|
descending.orElse(false), search.orElse(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@GetMapping("/pick")
|
||||||
|
@Transactional
|
||||||
|
public List<User> pick(
|
||||||
|
@RequestParam("filter") Optional<String> search) {
|
||||||
|
return userManager.fetch(5L, 0L, "", false, search.orElse("")).getResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@GetMapping("/{username}")
|
||||||
|
@Transactional
|
||||||
|
public User get(@PathVariable("username") String username) {
|
||||||
|
User user = userManager.getByUsername(username);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PostMapping
|
||||||
|
@Transactional
|
||||||
|
public User create(@RequestBody User user) {
|
||||||
|
Errors errors = new RequestBodyErrors(user);
|
||||||
|
userValidator.validateNew(user, errors);
|
||||||
|
|
||||||
|
if (errors.hasErrors()) {
|
||||||
|
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.getLocale() == null) {
|
||||||
|
user.setLocale("de");
|
||||||
|
}
|
||||||
|
|
||||||
|
return userManager.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PatchMapping
|
||||||
|
@Transactional
|
||||||
|
public User update(@RequestBody User user) {
|
||||||
|
Errors errors = new RequestBodyErrors(user);
|
||||||
|
userValidator.validate(user, errors);
|
||||||
|
|
||||||
|
if (errors.hasErrors()) {
|
||||||
|
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getCurrentUsername().equals(user.getUsername()) && user.getRoles().indexOf("ROLE_ADMIN") == -1) {
|
||||||
|
user.getRoles().add("ROLE_ADMIN");
|
||||||
|
}
|
||||||
|
|
||||||
|
return userManager.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@DeleteMapping("/{username}")
|
||||||
|
@Transactional
|
||||||
|
public void delete(@PathVariable("username") String username) {
|
||||||
|
User user = userManager.getByUsername(username);
|
||||||
|
|
||||||
|
if (user == null || getCurrentUsername().equals(username)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
userManager.delete(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PostMapping("/{username}/password")
|
||||||
|
public void password(@PathVariable("username") String username,
|
||||||
|
@RequestBody UserPasswordModel passwordModel) {
|
||||||
|
|
||||||
|
Errors errors = new RequestBodyErrors(passwordModel);
|
||||||
|
|
||||||
|
User user = userManager.getByUsername(username);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordModelValidator.validate(passwordModel, errors);
|
||||||
|
|
||||||
|
if (errors.hasErrors()) {
|
||||||
|
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
userManager.setPassword(user.getUsername(), passwordModel.getPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.admin.validation;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.validation.Errors;
|
||||||
|
import org.springframework.validation.Validator;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.businesslogic.UserManager;
|
||||||
|
import de.champonthis.buntspecht.model.User;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class UserValidator implements Validator {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserManager userManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return clazz.isAssignableFrom(User.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(Object target, Errors errors) {
|
||||||
|
User user = (User) target;
|
||||||
|
if (!StringUtils.hasText(user.getUsername())) {
|
||||||
|
errors.rejectValue("username", "REQUIRED");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateNew(Object target, Errors errors) {
|
||||||
|
validate(target, errors);
|
||||||
|
|
||||||
|
if (errors.hasErrors()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = (User) target;
|
||||||
|
if (userManager.exists(user.getUsername())) {
|
||||||
|
errors.rejectValue("username", "ALREADY_EXISTS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.model;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.boot.jackson.JsonComponent;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import com.querydsl.core.Tuple;
|
||||||
|
|
||||||
|
@JsonComponent
|
||||||
|
public class TupleSerializer extends JsonSerializer<Tuple> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(Tuple value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||||
|
if (value.toArray().length > 1) {
|
||||||
|
gen.writeStartArray();
|
||||||
|
}
|
||||||
|
for (Object object : value.toArray()) {
|
||||||
|
gen.writeObject(object);
|
||||||
|
}
|
||||||
|
if (value.toArray().length > 1) {
|
||||||
|
gen.writeEndArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.model;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public class TurnoverFilterModel {
|
||||||
|
|
||||||
|
private MinMax<Instant> created;
|
||||||
|
private MinMax<Instant> dueDate;
|
||||||
|
private MinMax<Instant> updated;
|
||||||
|
private String customer;
|
||||||
|
private String motif;
|
||||||
|
private MinMax<Float> price;
|
||||||
|
private MinMax<Float> timeInvestment;
|
||||||
|
|
||||||
|
public MinMax<Instant> getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(MinMax<Instant> created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinMax<Instant> getDueDate() {
|
||||||
|
return dueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDueDate(MinMax<Instant> dueDate) {
|
||||||
|
this.dueDate = dueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinMax<Instant> getUpdated() {
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdated(MinMax<Instant> updated) {
|
||||||
|
this.updated = updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCustomer() {
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomer(String customer) {
|
||||||
|
this.customer = customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMotif() {
|
||||||
|
return motif;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMotif(String motif) {
|
||||||
|
this.motif = motif;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinMax<Float> getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrice(MinMax<Float> price) {
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinMax<Float> getTimeInvestment() {
|
||||||
|
return timeInvestment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeInvestment(MinMax<Float> timeInvestment) {
|
||||||
|
this.timeInvestment = timeInvestment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MinMax<T> {
|
||||||
|
|
||||||
|
private T min;
|
||||||
|
private T max;
|
||||||
|
|
||||||
|
public MinMax(T min, T max) {
|
||||||
|
this.min = min;
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getMin() {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMin(T min) {
|
||||||
|
this.min = min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getMax() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMax(T max) {
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.model;
|
||||||
|
|
||||||
|
public class UserPasswordModel {
|
||||||
|
|
||||||
|
private String old;
|
||||||
|
private String password;
|
||||||
|
private String password2;
|
||||||
|
|
||||||
|
public String getOld() {
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOld(String old) {
|
||||||
|
this.old = old;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword2() {
|
||||||
|
return password2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword2(String password2) {
|
||||||
|
this.password2 = password2;
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.support;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||||
|
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
|
||||||
|
|
||||||
|
|
||||||
|
@ExceptionHandler(value = { EntityResponseStatusException.class })
|
||||||
|
protected ResponseEntity<Object> handleResponseEntityStatusException(RuntimeException exception,
|
||||||
|
WebRequest request) {
|
||||||
|
EntityResponseStatusException entityResponseStatusException = (EntityResponseStatusException) exception;
|
||||||
|
return handleExceptionInternal(exception, entityResponseStatusException.getBody(), new HttpHeaders(),
|
||||||
|
entityResponseStatusException.getStatus(), request);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.support;
|
||||||
|
|
||||||
|
import org.springframework.core.NestedRuntimeException;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import jakarta.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
|
public class EntityResponseStatusException extends NestedRuntimeException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final HttpStatus status;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Object body;
|
||||||
|
|
||||||
|
|
||||||
|
public EntityResponseStatusException(HttpStatus status) {
|
||||||
|
this(null, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public EntityResponseStatusException(@Nullable Object body, HttpStatus status) {
|
||||||
|
this(body, status, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public EntityResponseStatusException(@Nullable Object body, HttpStatus status, @Nullable Throwable cause) {
|
||||||
|
super(null, cause);
|
||||||
|
Assert.notNull(status, "HttpStatus is required");
|
||||||
|
this.status = status;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public HttpStatus getStatus() {
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Object getBody() {
|
||||||
|
return this.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.core.NestedRuntimeException#getMessage()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return this.status + (this.body != null ? " \"" + this.body + "\"" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.support;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpInputMessage;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||||
|
import org.springframework.http.server.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServerHttpResponse;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class JsonStringBodyControllerAdvice implements RequestBodyAdvice, ResponseBodyAdvice<String> {
|
||||||
|
|
||||||
|
private Gson gson = new Gson();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice#
|
||||||
|
* supports(org.springframework.core.MethodParameter, java.lang.reflect.Type,
|
||||||
|
* java.lang.Class)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean supports(MethodParameter methodParameter, Type targetType,
|
||||||
|
Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
|
return targetType instanceof Class && String.class.equals((Class<?>) targetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice#
|
||||||
|
* beforeBodyRead(org.springframework.http.HttpInputMessage,
|
||||||
|
* org.springframework.core.MethodParameter, java.lang.reflect.Type,
|
||||||
|
* java.lang.Class)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
|
||||||
|
Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
|
||||||
|
return inputMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice#
|
||||||
|
* afterBodyRead(java.lang.Object, org.springframework.http.HttpInputMessage,
|
||||||
|
* org.springframework.core.MethodParameter, java.lang.reflect.Type,
|
||||||
|
* java.lang.Class)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
|
||||||
|
Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
|
body = ((String) body).replaceAll("^\"|\"$", "");
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice#
|
||||||
|
* handleEmptyBody(java.lang.Object, org.springframework.http.HttpInputMessage,
|
||||||
|
* org.springframework.core.MethodParameter, java.lang.reflect.Type,
|
||||||
|
* java.lang.Class)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
|
||||||
|
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see
|
||||||
|
* org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#
|
||||||
|
* supports(org.springframework.core.MethodParameter, java.lang.Class)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
|
return converterType == StringHttpMessageConverter.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see
|
||||||
|
* org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#
|
||||||
|
* beforeBodyWrite(java.lang.Object, org.springframework.core.MethodParameter,
|
||||||
|
* org.springframework.http.MediaType, java.lang.Class,
|
||||||
|
* org.springframework.http.server.ServerHttpRequest,
|
||||||
|
* org.springframework.http.server.ServerHttpResponse)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String beforeBodyWrite(String body, MethodParameter returnType, MediaType selectedContentType,
|
||||||
|
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
|
||||||
|
ServerHttpResponse response) {
|
||||||
|
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
return gson.toJson(new JsonPrimitive(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.support;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.validation.AbstractBindingResult;
|
||||||
|
|
||||||
|
|
||||||
|
public class RequestBodyErrors extends AbstractBindingResult {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Object target;
|
||||||
|
|
||||||
|
|
||||||
|
public RequestBodyErrors(@Nullable Object target) {
|
||||||
|
super("request-body");
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.validation.AbstractBindingResult#getTarget()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see
|
||||||
|
* org.springframework.validation.AbstractBindingResult#getActualFieldValue(java
|
||||||
|
* .lang.String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Object getActualFieldValue(String field) {
|
||||||
|
// Not necessary
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.validation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.passay.CharacterRule;
|
||||||
|
import org.passay.EnglishCharacterData;
|
||||||
|
import org.passay.LengthRule;
|
||||||
|
import org.passay.PasswordData;
|
||||||
|
import org.passay.PasswordValidator;
|
||||||
|
import org.passay.Rule;
|
||||||
|
import org.passay.RuleResult;
|
||||||
|
import org.passay.RuleResultDetail;
|
||||||
|
import org.passay.WhitespaceRule;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.validation.Errors;
|
||||||
|
import org.springframework.validation.Validator;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.businesslogic.SystemPropertyManager;
|
||||||
|
import de.champonthis.buntspecht.controller.model.UserPasswordModel;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class PasswordModelValidator implements Validator {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemPropertyManager systemPropertyManager;
|
||||||
|
|
||||||
|
public static final String SYSTEM_PROPERTY_PASSWORD_RULE_WHITESPACE = "password.rule.whitespace";
|
||||||
|
public static final String SYSTEM_PROPERTY_PASSWORD_RULE_LENGTH = "password.rule.length";
|
||||||
|
public static final String SYSTEM_PROPERTY_PASSWORD_RULE_UPPERCASE = "password.rule.uppercase";
|
||||||
|
public static final String SYSTEM_PROPERTY_PASSWORD_RULE_DIGIT = "password.rule.digit";
|
||||||
|
public static final String SYSTEM_PROPERTY_PASSWORD_RULE_SPECIAL = "password.rule.special";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return clazz.isAssignableFrom(UserPasswordModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(Object target, Errors errors) {
|
||||||
|
UserPasswordModel passwordModel = (UserPasswordModel) target;
|
||||||
|
|
||||||
|
List<Rule> rules = new ArrayList<Rule>();
|
||||||
|
|
||||||
|
if (systemPropertyManager.getBoolean(SYSTEM_PROPERTY_PASSWORD_RULE_WHITESPACE, true)) {
|
||||||
|
rules.add(new WhitespaceRule());
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = systemPropertyManager.getInteger(SYSTEM_PROPERTY_PASSWORD_RULE_LENGTH, 8);
|
||||||
|
if (length > 0) {
|
||||||
|
rules.add(new LengthRule(length, 4096));
|
||||||
|
}
|
||||||
|
|
||||||
|
int uppercase = systemPropertyManager.getInteger(SYSTEM_PROPERTY_PASSWORD_RULE_UPPERCASE, 1);
|
||||||
|
if (uppercase > 0) {
|
||||||
|
rules.add(new CharacterRule(EnglishCharacterData.UpperCase, uppercase));
|
||||||
|
}
|
||||||
|
|
||||||
|
int digit = systemPropertyManager.getInteger(SYSTEM_PROPERTY_PASSWORD_RULE_DIGIT, 1);
|
||||||
|
if (digit > 0) {
|
||||||
|
rules.add(new CharacterRule(EnglishCharacterData.Digit, digit));
|
||||||
|
}
|
||||||
|
|
||||||
|
int special = systemPropertyManager.getInteger(SYSTEM_PROPERTY_PASSWORD_RULE_SPECIAL, 1);
|
||||||
|
if (special > 0) {
|
||||||
|
rules.add(new CharacterRule(EnglishCharacterData.Special, special));
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordValidator validator = new PasswordValidator(rules);
|
||||||
|
PasswordData password = new PasswordData(passwordModel.getPassword());
|
||||||
|
RuleResult result = validator.validate(password);
|
||||||
|
|
||||||
|
if (!result.isValid()) {
|
||||||
|
for (RuleResultDetail ruleResultDetail : result.getDetails()) {
|
||||||
|
errors.rejectValue("password", ruleResultDetail.getErrorCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passwordModel.getPassword().equals(passwordModel.getPassword2())) {
|
||||||
|
errors.rejectValue("password2", "NOT_MATCH");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package de.champonthis.buntspecht.controller.validation;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.validation.Errors;
|
||||||
|
import org.springframework.validation.Validator;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.model.Turnover;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TurnoverValidator implements Validator {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return clazz.isAssignableFrom(Turnover.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(Object target, Errors errors) {
|
||||||
|
Turnover turnover = (Turnover) target;
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(turnover.getCustomer())) {
|
||||||
|
errors.rejectValue("customer", "REQUIRED");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(turnover.getMotif())) {
|
||||||
|
errors.rejectValue("motif", "REQUIRED");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turnover.getPrice() == 0) {
|
||||||
|
errors.rejectValue("price", "REQUIRED");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turnover.getPrice() < 0) {
|
||||||
|
errors.rejectValue("price", "POSITIVE_VALUE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package de.champonthis.buntspecht.i18n.businesslogic;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.i18n.model.I18n;
|
||||||
|
import de.champonthis.buntspecht.i18n.repository.I18nRepository;
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class I18nManager implements SmartInitializingSingleton {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(I18nManager.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private I18nRepository i18nRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ResourceLoader resourceLoader;
|
||||||
|
|
||||||
|
private Gson gson = new Gson();
|
||||||
|
|
||||||
|
|
||||||
|
public I18n get(String locale) {
|
||||||
|
return i18nRepository.findById(locale).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public JsonObject getLabel(String locale) {
|
||||||
|
I18n i18n = get(locale);
|
||||||
|
if (i18n != null && StringUtils.hasText(i18n.getLabel())) {
|
||||||
|
JsonElement element = JsonParser.parseString(i18n.getLabel());
|
||||||
|
if (element != null && element.isJsonObject()) {
|
||||||
|
return element.getAsJsonObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<String> getLocales() {
|
||||||
|
return i18nRepository.findAll().stream().map(I18n::getLocale).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void extendJsonObject(JsonObject dest, JsonObject src) {
|
||||||
|
for (Entry<String, JsonElement> srcEntry : src.entrySet()) {
|
||||||
|
String srcKey = srcEntry.getKey();
|
||||||
|
JsonElement srcValue = srcEntry.getValue();
|
||||||
|
if (dest.has(srcKey)) {
|
||||||
|
JsonElement destValue = dest.get(srcKey);
|
||||||
|
if (destValue.isJsonObject() && srcValue.isJsonObject()) {
|
||||||
|
extendJsonObject(destValue.getAsJsonObject(), srcValue.getAsJsonObject());
|
||||||
|
} else {
|
||||||
|
dest.add(srcKey, srcValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dest.add(srcKey, srcValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public I18n addLabel(String locale, JsonObject newLabel) {
|
||||||
|
JsonObject label = getLabel(locale);
|
||||||
|
|
||||||
|
if (label == null || label.size() == 0 || label.entrySet().isEmpty()) {
|
||||||
|
label = newLabel;
|
||||||
|
} else {
|
||||||
|
extendJsonObject(label, newLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
I18n i18n = new I18n();
|
||||||
|
i18n.setLocale(locale);
|
||||||
|
i18n.setLabel(gson.toJson(label));
|
||||||
|
|
||||||
|
return i18nRepository.save(i18n);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public I18n setLabel(String locale, JsonObject label) {
|
||||||
|
I18n i18n = new I18n();
|
||||||
|
i18n.setLocale(locale);
|
||||||
|
i18n.setLabel(gson.toJson(label));
|
||||||
|
|
||||||
|
return i18nRepository.save(i18n);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void deleteLabel(String locale) {
|
||||||
|
if (i18nRepository.existsById(locale)) {
|
||||||
|
i18nRepository.deleteById(locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.beans.factory.SmartInitializingSingleton#
|
||||||
|
* afterSingletonsInstantiated()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void afterSingletonsInstantiated() {
|
||||||
|
try {
|
||||||
|
Resource resource = resourceLoader.getResource("classpath:label");
|
||||||
|
|
||||||
|
if (resource.exists()) {
|
||||||
|
File labelFolder = resource.getFile();
|
||||||
|
if (labelFolder.exists() && labelFolder.isDirectory()) {
|
||||||
|
for (File labelFile : labelFolder.listFiles()) {
|
||||||
|
JsonObject label = JsonParser.parseReader(new FileReader(labelFile, StandardCharsets.UTF_8))
|
||||||
|
.getAsJsonObject();
|
||||||
|
|
||||||
|
String locale = labelFile.getName().replace(".json", "");
|
||||||
|
addLabel(locale, label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("cannot read in label folder", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package de.champonthis.buntspecht.i18n.controller;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonIOException;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.controller.BaseController;
|
||||||
|
import de.champonthis.buntspecht.i18n.businesslogic.I18nManager;
|
||||||
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/i18n")
|
||||||
|
public class I18nController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private I18nManager i18nManager;
|
||||||
|
|
||||||
|
private Gson gson = new Gson();
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<String> getLocales() {
|
||||||
|
return i18nManager.getLocales();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping("/{locale}")
|
||||||
|
public void getLabel(@PathVariable("locale") String locale, HttpServletResponse response)
|
||||||
|
throws JsonIOException, IOException {
|
||||||
|
JsonObject label = i18nManager.getLabel(locale);
|
||||||
|
if (label != null) {
|
||||||
|
response.setCharacterEncoding("utf-8");
|
||||||
|
gson.toJson(label, response.getWriter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PostMapping("/{locale}")
|
||||||
|
public void setLabel(@PathVariable("locale") String locale, @RequestBody Object label) {
|
||||||
|
JsonElement element = gson.toJsonTree(label);
|
||||||
|
|
||||||
|
if (element != null && element.isJsonObject()) {
|
||||||
|
i18nManager.setLabel(locale, element.getAsJsonObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PutMapping("/{locale}")
|
||||||
|
public void addLabel(@PathVariable("locale") String locale, @RequestBody Object label) {
|
||||||
|
JsonElement element = gson.toJsonTree(label);
|
||||||
|
|
||||||
|
if (element != null && element.isJsonObject()) {
|
||||||
|
i18nManager.addLabel(locale, element.getAsJsonObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@DeleteMapping("/{locale}")
|
||||||
|
public void deleteLocale(@PathVariable("locale") String locale) {
|
||||||
|
i18nManager.deleteLabel(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package de.champonthis.buntspecht.i18n.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Lob;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "i18n", uniqueConstraints = @UniqueConstraint(columnNames = { "locale" }))
|
||||||
|
public class I18n {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name = "locale", unique = true, nullable = false)
|
||||||
|
private String locale;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Column(name = "label", length = 100000)
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
|
||||||
|
public String getLocale() {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setLocale(String locale) {
|
||||||
|
this.locale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setLabel(String label) {
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package de.champonthis.buntspecht.i18n.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.i18n.model.I18n;
|
||||||
|
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface I18nRepository extends JpaRepository<I18n, String>, QuerydslPredicateExecutor<I18n> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package de.champonthis.buntspecht.model;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "persistent_logins")
|
||||||
|
public class PersistentLogin {
|
||||||
|
|
||||||
|
@Column(name = "username", length = 64, nullable = false)
|
||||||
|
private String username;
|
||||||
|
@Id
|
||||||
|
@Column(name = "series", length = 64)
|
||||||
|
private String series;
|
||||||
|
@Column(name = "token", length = 64, nullable = false)
|
||||||
|
private String token;
|
||||||
|
@Column(name = "last_used", nullable = false)
|
||||||
|
private Instant last_used;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSeries() {
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeries(String series) {
|
||||||
|
this.series = series;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getLast_used() {
|
||||||
|
return last_used;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLast_used(Instant last_used) {
|
||||||
|
this.last_used = last_used;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package de.champonthis.buntspecht.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Lob;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "system_properties")
|
||||||
|
public class SystemProperty {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name = "id")
|
||||||
|
private String key;
|
||||||
|
@Lob
|
||||||
|
@Column(name = "value", length = 100000)
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
|
||||||
|
public SystemProperty() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public SystemProperty(String key, String value) {
|
||||||
|
super();
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
package de.champonthis.buntspecht.model;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Lob;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "turnovers")
|
||||||
|
public class Turnover {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name = "id", updatable = false, unique = true, nullable = false)
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "username", nullable = false)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Column(name = "created", nullable = false, updatable = false)
|
||||||
|
private Instant created;
|
||||||
|
|
||||||
|
@Column(name = "due_date", nullable = false)
|
||||||
|
private Instant dueDate;
|
||||||
|
|
||||||
|
@Column(name = "updated", nullable = false)
|
||||||
|
private Instant updated;
|
||||||
|
|
||||||
|
@Column(name = "customer", nullable = false)
|
||||||
|
private String customer;
|
||||||
|
|
||||||
|
@Column(name = "motif", nullable = false)
|
||||||
|
private String motif;
|
||||||
|
|
||||||
|
@Column(name = "price", nullable = false)
|
||||||
|
private float price;
|
||||||
|
|
||||||
|
@Column(name = "time_investment", nullable = true)
|
||||||
|
private float timeInvestment;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Column(name = "remark", nullable = true, length = 5000)
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Lob
|
||||||
|
@Column(name = "material_consumption", nullable = true, length = 5000)
|
||||||
|
private String materialConsumption;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreated(Instant created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getDueDate() {
|
||||||
|
return dueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDueDate(Instant dueDate) {
|
||||||
|
this.dueDate = dueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getUpdated() {
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdated(Instant updated) {
|
||||||
|
this.updated = updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCustomer() {
|
||||||
|
return customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomer(String customer) {
|
||||||
|
this.customer = customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMotif() {
|
||||||
|
return motif;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMotif(String motif) {
|
||||||
|
this.motif = motif;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrice(float price) {
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getTimeInvestment() {
|
||||||
|
return timeInvestment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeInvestment(float timeInvestment) {
|
||||||
|
this.timeInvestment = timeInvestment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRemark() {
|
||||||
|
return remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemark(String remark) {
|
||||||
|
this.remark = remark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMaterialConsumption() {
|
||||||
|
return materialConsumption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaterialConsumption(String materialConsumption) {
|
||||||
|
this.materialConsumption = materialConsumption;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof Turnover)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Turnover turnover = (Turnover) obj;
|
||||||
|
boolean equals = true;
|
||||||
|
|
||||||
|
equals &= dueDate.equals(turnover.getDueDate());
|
||||||
|
|
||||||
|
equals &= id == null && turnover.getId() == null || id.equals(turnover.getId());
|
||||||
|
|
||||||
|
equals &= username == null && turnover.getUsername() == null || username.equals(turnover.getUsername());
|
||||||
|
|
||||||
|
equals &= customer == null && turnover.getCustomer() == null || customer.equals(turnover.getCustomer());
|
||||||
|
|
||||||
|
equals &= motif == null && turnover.getMotif() == null || motif.equals(turnover.getMotif());
|
||||||
|
|
||||||
|
equals &= price == turnover.getPrice();
|
||||||
|
|
||||||
|
equals &= timeInvestment == turnover.getTimeInvestment();
|
||||||
|
|
||||||
|
equals &= remark == null && turnover.getRemark() == null || remark.equals(turnover.getRemark());
|
||||||
|
|
||||||
|
equals &= materialConsumption == null && turnover.getMaterialConsumption() == null
|
||||||
|
|| materialConsumption.equals(turnover.getMaterialConsumption());
|
||||||
|
|
||||||
|
return equals;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package de.champonthis.buntspecht.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||||
|
|
||||||
|
import jakarta.persistence.CollectionTable;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.ElementCollection;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Lob;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.Transient;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "users")
|
||||||
|
@JsonInclude(Include.NON_EMPTY)
|
||||||
|
public class User {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name = "username", nullable = false)
|
||||||
|
private String username;
|
||||||
|
@Column(name = "external_id", nullable = true)
|
||||||
|
private String externalId;
|
||||||
|
@Column(name = "name", nullable = true)
|
||||||
|
private String name;
|
||||||
|
@JsonIgnore
|
||||||
|
@Column(name = "password_hash", nullable = true)
|
||||||
|
private String passwordHash;
|
||||||
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
|
@CollectionTable(name = "users_roles")
|
||||||
|
private List<String> roles;
|
||||||
|
@Lob
|
||||||
|
@Column(name = "about", nullable = true, length = 100000)
|
||||||
|
private String about;
|
||||||
|
@Column(name = "email", nullable = true)
|
||||||
|
private String email;
|
||||||
|
@Column(name = "locale", nullable = true, columnDefinition = "varchar(255) default 'de'")
|
||||||
|
private String locale;
|
||||||
|
@Column(name = "dark_theme", nullable = true, columnDefinition = "boolean default false")
|
||||||
|
private boolean darkTheme;
|
||||||
|
@Transient
|
||||||
|
private Map<String, Object> metadata;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExternalId() {
|
||||||
|
return externalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExternalId(String externalId) {
|
||||||
|
this.externalId = externalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPasswordHash() {
|
||||||
|
return passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordHash(String passwordHash) {
|
||||||
|
this.passwordHash = passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getRoles() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoles(List<String> roles) {
|
||||||
|
this.roles = roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAbout() {
|
||||||
|
return about;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAbout(String about) {
|
||||||
|
this.about = about;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocale() {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocale(String locale) {
|
||||||
|
this.locale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDarkTheme() {
|
||||||
|
return darkTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDarkTheme(boolean darkTheme) {
|
||||||
|
this.darkTheme = darkTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getMetadata() {
|
||||||
|
if (metadata == null) {
|
||||||
|
metadata = Map.of();
|
||||||
|
}
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetadata(Map<String, Object> metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package de.champonthis.buntspecht.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.model.SystemProperty;
|
||||||
|
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface SystemPropertyRepository
|
||||||
|
extends JpaRepository<SystemProperty, String>, QuerydslPredicateExecutor<SystemProperty> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package de.champonthis.buntspecht.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.model.Turnover;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface TurnoverRepository extends JpaRepository<Turnover, Long>, QuerydslPredicateExecutor<Turnover> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package de.champonthis.buntspecht.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.model.User;
|
||||||
|
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserRepository extends JpaRepository<User, String>, QuerydslPredicateExecutor<User> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package de.champonthis.buntspecht.security;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
|
||||||
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
|
|
||||||
|
|
||||||
|
public class LocalRememberMeServices extends PersistentTokenBasedRememberMeServices {
|
||||||
|
|
||||||
|
|
||||||
|
public LocalRememberMeServices(String key, UserDetailsService userDetailsService,
|
||||||
|
PersistentTokenRepository tokenRepository) {
|
||||||
|
super(key, userDetailsService, tokenRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.security.web.authentication.rememberme.
|
||||||
|
* AbstractRememberMeServices#rememberMeRequested(javax.servlet.http.
|
||||||
|
* HttpServletRequest, java.lang.String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
|
||||||
|
Object value = request.getAttribute(parameter);
|
||||||
|
if (value != null) {
|
||||||
|
String paramValue = value.toString();
|
||||||
|
if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")
|
||||||
|
|| paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.rememberMeRequested(request, parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package de.champonthis.buntspecht.security;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
|
||||||
|
|
||||||
|
public class LocalUserDetails extends User {
|
||||||
|
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|
||||||
|
public LocalUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) {
|
||||||
|
super(username, password, authorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package de.champonthis.buntspecht.security;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class PasswordEncoderConfig {
|
||||||
|
|
||||||
|
|
||||||
|
@Bean(name = "passwordEncoder")
|
||||||
|
public Argon2PasswordEncoder passwordEncoder() {
|
||||||
|
return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
|
||||||
|
}
|
||||||
|
}
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
package de.champonthis.buntspecht.security;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||||
|
import org.springframework.security.web.authentication.RememberMeServices;
|
||||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
|
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
|
||||||
|
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
|
||||||
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
|
||||||
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.businesslogic.UserManager;
|
||||||
|
import de.champonthis.buntspecht.security.handler.FormAuthenticationFailureHandler;
|
||||||
|
import de.champonthis.buntspecht.security.handler.OAuth2AuthenticationSuccessHandler;
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity(prePostEnabled = true)
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserManager userManager;
|
||||||
|
@Autowired
|
||||||
|
private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
|
||||||
|
@Autowired
|
||||||
|
private DataSource dataSource;
|
||||||
|
@Value("${loginUrl:/login}")
|
||||||
|
private String loginUrl;
|
||||||
|
@Value("${loginTargetUrl:/}")
|
||||||
|
private String loginTargetUrl;
|
||||||
|
@Value("${spring.security.oauth2.client:false}")
|
||||||
|
private boolean oauth2Enabled;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
|
if (oauth2Enabled) {
|
||||||
|
oAuth2AuthenticationSuccessHandler.setDefaultTargetUrl(loginTargetUrl);
|
||||||
|
oAuth2AuthenticationSuccessHandler.setRememberMeServices(rememberMeServices());
|
||||||
|
}
|
||||||
|
|
||||||
|
http
|
||||||
|
// crsf
|
||||||
|
.csrf((csrf) -> csrf.disable())
|
||||||
|
// cors
|
||||||
|
// .cors().configurationSource(corsConfigurationSource()).and()
|
||||||
|
// anonymous
|
||||||
|
.anonymous((anonymous) -> anonymous.disable())
|
||||||
|
// login
|
||||||
|
.formLogin((formLogin) -> formLogin.loginPage("/login").defaultSuccessUrl(loginTargetUrl)
|
||||||
|
.failureHandler(new FormAuthenticationFailureHandler(loginUrl)))
|
||||||
|
// remember me
|
||||||
|
.rememberMe((rememberMe) -> rememberMe.rememberMeServices(rememberMeServices()))
|
||||||
|
// logout
|
||||||
|
.logout((logout) -> logout.logoutUrl("/logout")
|
||||||
|
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)))
|
||||||
|
// exception
|
||||||
|
.exceptionHandling((exceptionHandling) -> exceptionHandling
|
||||||
|
.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
|
||||||
|
new AntPathRequestMatcher("/api/**")));
|
||||||
|
|
||||||
|
if (oauth2Enabled) {
|
||||||
|
http.oauth2Login((oauth2Login) -> oauth2Login.successHandler(oAuth2AuthenticationSuccessHandler)
|
||||||
|
.failureHandler(new SimpleUrlAuthenticationFailureHandler(loginUrl + "?externalError"))
|
||||||
|
.loginPage("/login"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PersistentTokenRepository persistentTokenRepository() {
|
||||||
|
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
|
||||||
|
tokenRepository.setDataSource(dataSource);
|
||||||
|
return tokenRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RememberMeServices rememberMeServices() {
|
||||||
|
PersistentTokenBasedRememberMeServices rememberMeServices = new LocalRememberMeServices("remember-me",
|
||||||
|
userManager, persistentTokenRepository());
|
||||||
|
return rememberMeServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
|
configuration.setAllowedOriginPatterns(List.of("*"));
|
||||||
|
configuration.setAllowedMethods(Collections.singletonList("*"));
|
||||||
|
configuration.setAllowCredentials(true);
|
||||||
|
configuration.setAllowedHeaders(Collections.singletonList("*"));
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package de.champonthis.buntspecht.security.handler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
public class FormAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||||
|
|
||||||
|
private String failureUrl;
|
||||||
|
|
||||||
|
public FormAuthenticationFailureHandler(String failureUrl) {
|
||||||
|
super(failureUrl);
|
||||||
|
this.failureUrl = failureUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
AuthenticationException exception) throws IOException, ServletException {
|
||||||
|
setDefaultFailureUrl(failureUrl + "?error&username=" + request.getParameter("username"));
|
||||||
|
super.onAuthenticationFailure(request, response, exception);
|
||||||
|
setDefaultFailureUrl(failureUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
package de.champonthis.buntspecht.security.handler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.web.authentication.RememberMeServices;
|
||||||
|
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import de.champonthis.buntspecht.businesslogic.UserManager;
|
||||||
|
import de.champonthis.buntspecht.model.User;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class OAuth2AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserManager userManager;
|
||||||
|
|
||||||
|
private RememberMeServices rememberMeServices;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.security.web.authentication.
|
||||||
|
* SavedRequestAwareAuthenticationSuccessHandler#onAuthenticationSuccess(javax.
|
||||||
|
* servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse,
|
||||||
|
* org.springframework.security.core.Authentication)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Authentication authentication) throws IOException, ServletException {
|
||||||
|
User user = userManager.getByAuth(authentication);
|
||||||
|
|
||||||
|
UserDetails userDetails = userManager.loadUserByUsername(user.getUsername());
|
||||||
|
|
||||||
|
List<GrantedAuthority> authorities = new ArrayList<>();
|
||||||
|
authorities.addAll(authentication.getAuthorities());
|
||||||
|
authorities.addAll(userDetails.getAuthorities());
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(userDetails,
|
||||||
|
null, authorities);
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(newAuthentication);
|
||||||
|
|
||||||
|
if (rememberMeServices != null) {
|
||||||
|
request.setAttribute("remember-me", "true");
|
||||||
|
rememberMeServices.loginSuccess(request, response, newAuthentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
handle(request, response, newAuthentication);
|
||||||
|
clearAuthenticationAttributes(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setRememberMeServices(RememberMeServices rememberMeServices) {
|
||||||
|
this.rememberMeServices = rememberMeServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user