Compare commits

..

18 Commits

25 changed files with 289 additions and 102 deletions

20
.gitignore vendored
View File

@ -8,24 +8,4 @@ hs_err*.log
application.properties
usernames.txt
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# System files
.DS_Store
Thumbs.db

View File

@ -109,6 +109,9 @@
<scope>runtime</scope>
</dependency>
</dependencies>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>db-mysql</id>
@ -130,6 +133,16 @@
</dependency>
</dependencies>
</profile>
<profile>
<id>developer</id>
<dependencies>
<dependency>
<groupId>de.bstly.we</groupId>
<artifactId>webstly-developer</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</profile>
</profiles>
<build>

View File

@ -38,7 +38,7 @@ public class BorrowItem implements UserData {
@Column(name = "name", nullable = false)
private String name;
@Lob
@Column(name = "description", nullable = true)
@Column(name = "description", nullable = true, length = 100000)
private String description;
@Column(name = "url", nullable = true)
private String url;

View File

@ -41,10 +41,10 @@ public class BorrowRequest implements UserData {
@Column(name = "ends", nullable = false)
private Instant ends;
@Lob
@Column(name = "reply", nullable = true)
@Column(name = "reply", nullable = true, length = 100000)
private String reply;
@Lob
@Column(name = "comment", nullable = true)
@Column(name = "comment", nullable = true, length = 100000)
private String comment;
@Transient
private BorrowItem borrowItem;

View File

@ -56,13 +56,13 @@
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.7</version>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
<version>33.3.1-jre</version>
</dependency>
<dependency>
@ -73,7 +73,7 @@
<dependency>
<groupId>org.passay</groupId>
<artifactId>passay</artifactId>
<version>1.6.4</version>
<version>1.6.5</version>
</dependency>
<dependency>
@ -84,8 +84,8 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>
<dependency>
@ -96,13 +96,13 @@
<dependency>
<groupId>javax.measure</groupId>
<artifactId>unit-api</artifactId>
<version>2.1.3</version>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.9.0</version>
<version>1.12.0</version>
</dependency>
</dependencies>
</project>

View File

@ -416,16 +416,15 @@ public class PermissionManager implements UserDataProvider {
List<Permission> existingPermissions = get(target, name);
for (Permission existingPermission : existingPermissions) {
if (existingPermission.getExpires().isBefore(permissionsExpires)) {
if (starts != null) {
if (existingPermission.getStarts() == null || existingPermission.getStarts().isAfter(starts)
|| existingPermission.getExpires().isAfter(starts)) {
if (additional && (starts == null && (existingPermission.getStarts() == null
|| existingPermission.getStarts().isBefore(Instant.now()))
|| starts != null && (starts.isBefore(existingPermission.getExpires())
|| existingPermission.getStarts() != null
&& starts.isAfter(existingPermission.getStarts())))) {
if (permission == null) {
permission = existingPermission;
break;
}
} else if (existingPermission.getStarts() == null) {
} else if (existingPermission.getExpires().isAfter(permission.getExpires())) {
permission = existingPermission;
break;
}
}
}
@ -434,23 +433,26 @@ public class PermissionManager implements UserDataProvider {
permission = new Permission();
permission.setTarget(target);
permission.setName(name);
permission.setAddon(permissionMapping.isAddon());
permission.setAddon(permissionMapping.isAddon()); // TODO: check for ROLE_MEMBER
permission.setStarts(permissionStarts);
permission.setExpires(permissionsExpires);
} else {
if (permission.getStarts() != null && permission.getStarts().isBefore(Instant.now())) {
permission.setStarts(null);
if (permission.getExpires() == null || permission.getExpires().isBefore(Instant.now())) {
permission.setExpires(Instant.now());
}
permission.setExpires(InstantHelper.plus(permission.getExpires(), permissionMapping.getLifetime(),
permissionMapping.getLifetimeUnit()));
}
if (permission.getStarts() != null && permission.getStarts().isBefore(Instant.now())) {
permission.setStarts(null);
}
if (permissionMapping.isLifetimeRound()) {
permission.setExpires(
InstantHelper.truncate(permission.getExpires(), permissionMapping.getLifetimeUnit()));
}
permissions.add(permission);
}
}

View File

@ -525,12 +525,16 @@ public class PretixManager implements SmartInitializingSingleton {
if (payload != null) {
request.bodyValue(gson.toJson(payload));
}
try {
String jsonString = request.retrieve().bodyToMono(String.class).block();
if (StringUtils.hasText(jsonString)) {
return JsonParser.parseString(jsonString);
}
} catch (WebClientResponseException e) {
logger.warn("Error response: " + e.getResponseBodyAsString(), e);
throw e;
}
return null;
}

View File

@ -20,6 +20,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.beust.jcommander.internal.Lists;
import de.bstly.we.businesslogic.PretixManager;
import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.model.SystemProperty;
@ -90,6 +92,17 @@ public class SystemController extends BaseController {
return systemPropertyRepository.save(systemProperty);
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/properties/list")
public List<SystemProperty> createOrUpdateList(@RequestBody List<SystemProperty> systemProperties) {
List<SystemProperty> result = Lists.newArrayList();
for (SystemProperty systemProperty : systemProperties) {
result.add(
systemPropertyRepository.save(systemProperty));
}
return result;
}
/**
* Delete property.
*

View File

@ -7,19 +7,19 @@ 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.HttpStatus;
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.util.MimeType;
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;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
/**
* The Class JsonStringBodyControllerAdvice.
@ -27,8 +27,6 @@ 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,
@ -49,6 +47,11 @@ public class JsonStringBodyControllerAdvice implements RequestBodyAdvice, Respon
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
if (inputMessage.getHeaders() == null || inputMessage.getHeaders().getContentType() == null || !inputMessage
.getHeaders().getContentType().equalsTypeAndSubtype(MimeType.valueOf("application/json"))) {
throw new EntityResponseStatusException(HttpStatus.UNSUPPORTED_MEDIA_TYPE);
}
return inputMessage;
}
@ -84,7 +87,7 @@ public class JsonStringBodyControllerAdvice implements RequestBodyAdvice, Respon
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return converterType == StringHttpMessageConverter.class;
return returnType.getParameterType() == String.class;
}
/*
@ -99,8 +102,11 @@ public class JsonStringBodyControllerAdvice implements RequestBodyAdvice, Respon
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));
if (body == null) {
return "";
}
JsonElement json = JsonParser.parseString(body);
return json.toString();
}
}

View File

@ -260,9 +260,9 @@ public class TokenSessionManager {
for (String token : ((String) sessionAttribute).split(",")) {
if (!token.equals(secret)) {
if (StringUtils.hasLength(tokens)) {
tokens += "," + secret;
tokens += "," + token;
} else {
tokens = secret;
tokens = token;
}
}
}

View File

@ -32,7 +32,7 @@ public class UserProfileField implements UserData {
@Column(name = "value", nullable = true)
private String value;
@Lob
@Column(name = "blob_value", nullable = true)
@Column(name = "blob_value", nullable = true, length = 100000)
private String blob;
@Column(name = "type", nullable = false)
private ProfileFieldType type;

View File

@ -95,9 +95,7 @@ public class SecurityConfig {
.sessionManagement((anonymous) -> anonymous.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.sessionAuthenticationStrategy(new SessionFixationProtectionStrategy()))
// disable deprectated xss protection, x-frame
.headers((headers) -> headers.xssProtection((xssProtection) -> xssProtection.disable())
.frameOptions((frameOptions) -> frameOptions.disable()
.referrerPolicy((referrerPolicy) -> referrerPolicy.policy(ReferrerPolicy.UNSAFE_URL))))
.headers((headers) -> headers.xssProtection((xssProtection) -> xssProtection.disable()))
// form login
.formLogin((formLogin) -> formLogin.loginPage(loginUrl).usernameParameter("username")
.passwordParameter("password")
@ -119,8 +117,13 @@ public class SecurityConfig {
.exceptionHandling(
(exceptionHandling) -> exceptionHandling.accessDeniedHandler(localAccessDeniedHandler)
.authenticationEntryPoint(localAuthenticationEntryPoint()))
// x-frame
.headers((headers) -> headers.frameOptions((frameOptions) -> frameOptions.disable()
.referrerPolicy((referrerPolicy) -> referrerPolicy.policy(ReferrerPolicy.UNSAFE_URL))))
// crsf
.csrf((csrf) -> csrf.disable());
.csrf((csrf) -> csrf.disable())
// TODO: update
.securityContext((securityContext) -> securityContext.requireExplicitSave(false));
if (disableCors) {
http.cors((cors) -> cors.disable());

31
developer/pom.xml Executable file
View File

@ -0,0 +1,31 @@
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.bstly.we</groupId>
<artifactId>webstly-main</artifactId>
<version>${revision}</version>
</parent>
<properties>
<springdoc-openapi-ui.version>1.6.9</springdoc-openapi-ui.version>
</properties>
<name>developer</name>
<artifactId>webstly-developer</artifactId>
<dependencies>
<dependency>
<groupId>de.bstly.we</groupId>
<artifactId>webstly-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc-openapi-ui.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,51 @@
/**
*
*/
package de.bstly.we.developer;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.GroupedOpenApi.Builder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.info.BuildProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
/**
* The SpringDocConfig Class.
*/
@Configuration
public class SpringDocConfig {
@Autowired
private BuildProperties buildProperties;
/**
* api.
*
* @return GroupedOpenApi
*/
@Bean
public GroupedOpenApi api() {
Builder builder = GroupedOpenApi.builder();
builder.group("<ALL MODULES>").packagesToScan("de.bstly.we");
return builder.build();
}
/**
* apiInfo.
*
* @return OpenAPI
*/
@Bean
public OpenAPI apiInfo() {
return new OpenAPI()
.info(new Info()
.title("we.bstly - Api").contact(new Contact().name("Bastelei e. V.")
.url("https://www.bstly.de").email("api@bstly.de"))
.version(buildProperties.getVersion()));
}
}

View File

@ -0,0 +1,75 @@
/**
*
*/
package de.bstly.we.dyndns.businesslogic;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.xbill.DNS.Message;
import org.xbill.DNS.Name;
import org.xbill.DNS.Resolver;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.TSIG;
import org.xbill.DNS.Type;
import org.xbill.DNS.Update;
import de.bstly.we.businesslogic.SystemPropertyManager;
/**
* The Class DyndnsTokenManager.
*/
@Component
public class DyndnsManager {
public static final String SYSTEM_PROPERTY_DYNDNS_HOSTNAME = "dyndns.hostname";
public static final String SYSTEM_PROPERTY_DYNDNS_KEY_NAME = "dyndns.keyname";
public static final String SYSTEM_PROPERTY_DYNDNS_KEY = "dyndns.key";
public static final String SYSTEM_PROPERTY_DYNDNS_ZONE = "dyndns.zone";
public static final String SYSTEM_PROPERTY_DYNDNS_TTL = "dyndns.ttl";
private Logger logger = LoggerFactory.getLogger(DyndnsManager.class);
@Autowired
private SystemPropertyManager systemPropertyManager;
public void setRecord(String name, Optional<String> ip, Optional<String> ipv6) throws Exception {
String zoneValue = systemPropertyManager.get(SYSTEM_PROPERTY_DYNDNS_ZONE);
if (!StringUtils.hasText(zoneValue)) {
logger.warn("Missing DynDNS zone, cannot set record");
return;
}
long ttl = systemPropertyManager.getLong(SYSTEM_PROPERTY_DYNDNS_TTL, 300);
Name zone = Name.fromString(zoneValue);
Name host = Name.fromString(name.toLowerCase(), zone);
Update update = new Update(zone);
if (ip.isPresent()) {
update.replace(host, Type.A, ttl, ip.get());
}
if (ipv6.isPresent()) {
update.replace(host, Type.AAAA, ttl, ipv6.get());
}
String hostname = systemPropertyManager.get(SYSTEM_PROPERTY_DYNDNS_HOSTNAME, "127.0.0.1");
String keyName = systemPropertyManager.get(SYSTEM_PROPERTY_DYNDNS_KEY_NAME, "");
String key = systemPropertyManager.get(SYSTEM_PROPERTY_DYNDNS_KEY, "");
Resolver res = new SimpleResolver(hostname);
res.setTSIGKey(new TSIG(TSIG.HMAC_SHA512, keyName, key));
res.setTCP(true);
Message response = res.send(update);
logger.debug(
"\nDynDNS for '" + name + "' with\nIP:\t" + ip.orElse("-") + "\nIPV6:\t" + ipv6.orElse("-") + "\n\n" +
response.toString());
}
}

View File

@ -12,18 +12,12 @@ 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 org.xbill.DNS.Name;
import org.xbill.DNS.Resolver;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.TSIG;
import org.xbill.DNS.Type;
import org.xbill.DNS.Update;
import de.bstly.we.businesslogic.PermissionManager;
import de.bstly.we.businesslogic.SystemPropertyManager;
import de.bstly.we.businesslogic.UserAliasManager;
import de.bstly.we.controller.BaseController;
import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.dyndns.businesslogic.DyndnsManager;
import de.bstly.we.dyndns.businesslogic.DyndnsTokenManager;
import de.bstly.we.dyndns.businesslogic.DyndnsTokenPermissions;
import de.bstly.we.dyndns.model.DyndnsToken;
@ -36,19 +30,16 @@ import de.bstly.we.model.User;
@RequestMapping("/dyndns")
public class DyndnsController extends BaseController {
public static final String SYSTEM_PROPERTY_DYNDNS_HOSTNAME = "dyndns.hostname";
public static final String SYSTEM_PROPERTY_DYNDNS_KEY = "dyndns.key";
@Autowired
private UserAliasManager userAliasManager;
@Autowired
private DyndnsTokenManager dyndnsTokenManager;
@Autowired
private DyndnsManager dyndnsManager;
@Autowired
private PermissionManager permissionManager;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private SystemPropertyManager systemPropertyManager;
@GetMapping
public void updateDns(@RequestParam("username") String name, @RequestParam("token") String token,
@ -78,25 +69,9 @@ public class DyndnsController extends BaseController {
}
try {
Name zone = Name.fromString(name + ".we.bstly.de.");
Name host = Name.fromString("host", zone);
Update update = new Update(zone);
if (ip.isPresent()) {
update.replace(host, Type.A, 3600, ip.get());
}
if (ipv6.isPresent()) {
update.replace(host, Type.AAAA, 3600, ipv6.get());
}
String hostname = systemPropertyManager.get(SYSTEM_PROPERTY_DYNDNS_HOSTNAME, "127.0.0.1");
String key = systemPropertyManager.get(SYSTEM_PROPERTY_DYNDNS_KEY, "");
Resolver res = new SimpleResolver(hostname);
res.setTSIGKey(new TSIG(TSIG.HMAC_SHA512, host, key));
res.setTCP(true);
res.send(update);
dyndnsManager.setRecord(name, ip, ipv6);
} catch (Exception e) {
e.printStackTrace();
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
}
}

View File

@ -33,6 +33,7 @@ public class Invite implements UserData {
private Instant starts;
private Instant expires;
@Lob
@Column(length = 100000)
private String message;
private String note;
private boolean redeemed;

View File

@ -37,17 +37,22 @@ public class JitsiRoom implements UserData {
private Instant expires;
@Lob
@JsonIgnore
@Column(length = 100000)
private String token;
@Lob
@JsonIgnore
@Column(length = 100000)
private String moderationToken;
@Lob
@Column(length = 100000)
private String url;
@Lob
@Column(length = 100000)
private String moderationUrl;
@Reference
private String code;
@Lob
@Column(length = 100000)
private String orgUrl;
/**

View File

@ -458,11 +458,27 @@ public class MembershipManager {
membershipFeeOrder = pretixManager.createOrder(membershipFeeOrder);
JsonObject expire = new JsonObject();
expire.addProperty("expires", pretixDateFormat.format(Date.from(expires)));
membershipFeeOrder = pretixManager.extendOrder(membershipFeeOrder.get("code").getAsString(), expire);
logger.debug("Create membership fee order: " + membershipFeeOrder.toString());
/*
* JsonObject expire = new JsonObject();
* expire.addProperty("expires", pretixDateFormat.format(Date.from(expires)));
* membershipFeeOrder =
* pretixManager.extendOrder(membershipFeeOrder.get("code").getAsString(),
* expire);
*
* logger.debug("Extend membership fee order: " +
* membershipFeeOrder.get("code").getAsString() + " to "
* + pretixDateFormat.format(Date.from(expires)));
*/
/*
* if (orderSendmail) {
* pretixManager.sendEmail(membershipFeeOrder.get("code").getAsString());
* logger.debug("Resend link membership fee order: " +
* membershipFeeOrder.get("code").getAsString());
* }
*/
} else
// send reminder mail for membership fee order
if (membershipfeeReminderDays > 0 && membershipfeeReminderQuestion > 0

View File

@ -17,5 +17,13 @@
<artifactId>webstly-core</artifactId>
<version>${revision}</version>
</dependency>
<!-- Query DSL -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<classifier>jakarta</classifier>
</dependency>
</dependencies>
</project>

View File

@ -24,7 +24,7 @@ public class MinetestAccount implements UserData {
@Column(name = "owner")
private Long owner;
@Lob
@Column(name = "skin")
@Column(name = "skin", length = 100000)
private String skin;
/**

View File

@ -25,6 +25,7 @@ public class ParteyUserReport {
private Long reporter;
private Long user;
@Lob
@Column(length = 100000)
private String comment;
private String world;
private Instant created;

View File

@ -43,8 +43,8 @@ public class Timeslot implements UserData {
private Visibility visibility;
@Column(name = "title", nullable = true)
private String title;
@Column(name = "description", nullable = true)
@Lob
@Column(name = "description", nullable = true, length = 100000)
private String description;
@Column(name = "share", nullable = true)
private String share;

View File

@ -14,13 +14,13 @@
<java.version>17</java.version>
<querydsl.version>5.0.0</querydsl.version>
<nimbus.version>9.37.3</nimbus.version>
<revision>3.0.0-SNAPSHOT</revision>
<revision>3.0.6-SNAPSHOT</revision>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<version>3.4.0</version>
<relativePath />
</parent>
@ -28,6 +28,7 @@
<module>application</module>
<module>borrow</module>
<module>core</module>
<module>developer</module>
<module>dyndns</module>
<module>email</module>
<module>i18n</module>

View File

@ -34,6 +34,7 @@ public class ShortenedUrl implements UserData {
private String code;
private Long owner;
@Lob
@Column(length = 100000)
private String url;
private Instant expires;
@JsonIgnore
@ -41,6 +42,7 @@ public class ShortenedUrl implements UserData {
private String passwordHash;
private String link;
@Lob
@Column(length = 100000)
private String note;
@Column(name = "addon", columnDefinition = "boolean default false")
private boolean queryParameters;