initial commit

This commit is contained in:
2021-10-03 17:07:01 +02:00
commit 456332f24e
246 changed files with 24590 additions and 0 deletions
Executable
+31
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>
<name>jitsi</name>
<artifactId>webstly-jitsi</artifactId>
<dependencies>
<dependency>
<groupId>de.bstly.we</groupId>
<artifactId>webstly-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>de.bstly.we</groupId>
<artifactId>webstly-urlshortener</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,13 @@
/**
*
*/
package de.bstly.we.jitsi.businesslogic;
/**
* @author _bastler@bstly.de
*
*/
public interface JitsiPermissions {
public static final String JITSI_MEET = "jitsi";
}
@@ -0,0 +1,13 @@
/**
*
*/
package de.bstly.we.jitsi.businesslogic;
/**
* @author _bastler@bstly.de
*
*/
public interface JitsiQuotas {
public static final String JITSI_MEET = "jitsi";
}
@@ -0,0 +1,325 @@
/**
*
*/
package de.bstly.we.jitsi.businesslogic;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.google.common.collect.Lists;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.KeyLengthException;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTClaimsSet.Builder;
import com.nimbusds.jwt.SignedJWT;
import de.bstly.we.businesslogic.QuotaManager;
import de.bstly.we.businesslogic.UserDataProvider;
import de.bstly.we.businesslogic.UserManager;
import de.bstly.we.jitsi.model.JitsiRoom;
import de.bstly.we.jitsi.model.QJitsiRoom;
import de.bstly.we.jitsi.repository.JitsiRoomRepository;
import de.bstly.we.model.Quota;
import de.bstly.we.model.UserData;
import de.bstly.we.urlshortener.businesslogic.ShortenedUrlManager;
import de.bstly.we.urlshortener.businesslogic.ShortenedUrlQuotas;
import de.bstly.we.urlshortener.model.QShortenedUrl;
import de.bstly.we.urlshortener.model.ShortenedUrl;
import de.bstly.we.urlshortener.repository.ShortenedUrlRepository;
/**
* @author _bastler@bstly.de
*
*/
@Component
public class JitsiRoomManager implements SmartInitializingSingleton, UserDataProvider {
@Autowired
private UserManager userManager;
@Autowired
private JitsiRoomRepository jitsiRoomRepository;
@Autowired
private ShortenedUrlRepository shortenedUrlRepository;
@Autowired
private QuotaManager quotaManager;
@Autowired
private ShortenedUrlManager shortenedUrlManager;
private QShortenedUrl qShortenedUrl = QShortenedUrl.shortenedUrl;
private QJitsiRoom qJitsiRoom = QJitsiRoom.jitsiRoom;
private JWSSigner signer;
@Value("${jitsi.iss:}")
private String iss;
@Value("${jitsi.sub:}")
private String sub;
@Value("${jitsi.secret:}")
private String secret;
@Value("${jitsi.urlFormat:%s/jwt?%s}")
private String urlFormat;
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#
* afterSingletonsInstantiated()
*/
@Override
public void afterSingletonsInstantiated() {
try {
signer = new MACSigner(secret);
} catch (KeyLengthException e) {
e.printStackTrace();
}
}
/**
* @param id
* @return
*/
public JitsiRoom get(Long id) {
return jitsiRoomRepository.findById(id).orElse(null);
}
/**
*
* @param owner
* @return
*/
public List<JitsiRoom> getAllByOwner(Long owner) {
return Lists.newArrayList(jitsiRoomRepository.findAll(qJitsiRoom.owner.eq(owner)));
}
/**
*
* @param page
* @param size
* @param sortBy
* @param descending
* @return
*/
public Page<JitsiRoom> get(int page, int size, String sortBy, boolean descending) {
return jitsiRoomRepository.findAll(PageRequest.of(page, size,
descending ? Sort.by(sortBy).descending() : Sort.by(sortBy).ascending()));
}
/**
*
* @param userId
* @return
*/
public Page<JitsiRoom> getForUserId(Long userId, int page, int size, String sortBy,
boolean descending) {
// delete expired rooms
jitsiRoomRepository.deleteAll(jitsiRoomRepository.findAll(
qJitsiRoom.owner.eq(userId).and(qJitsiRoom.expires.before(Instant.now()))));
return jitsiRoomRepository.findAll(qJitsiRoom.owner.eq(userId), PageRequest.of(page, size,
descending ? Sort.by(sortBy).descending() : Sort.by(sortBy).ascending()));
}
/**
*
* @param owner
* @param room
* @param starts
* @param expires
* @return
* @throws JOSEException
*/
public JitsiRoom create(Long owner, String room, Instant starts, Instant moderationStarts,
Instant expires) throws JOSEException {
JitsiRoom jitsiRoom = new JitsiRoom();
jitsiRoom.setOwner(owner);
jitsiRoom.setRoom(room);
jitsiRoom.setStarts(starts);
jitsiRoom.setModerationStarts(moderationStarts);
jitsiRoom.setExpires(expires);
return save(jitsiRoom);
}
/**
*
* @param jitsiRoom
* @return
* @throws JOSEException
*/
public JitsiRoom save(JitsiRoom jitsiRoom) throws JOSEException {
Assert.notNull(jitsiRoom.getRoom(), "No domain defined!");
Assert.notNull(jitsiRoom.getOwner(), "No owner defined!");
Assert.notNull(userManager.get(jitsiRoom.getOwner()), "Invalid owner defined!");
Builder bodyBuilder = new JWTClaimsSet.Builder();
bodyBuilder.audience("jitsi");
bodyBuilder.subject(sub);
bodyBuilder.issuer(iss);
bodyBuilder.claim("room", jitsiRoom.getRoom());
bodyBuilder.claim("moderator", false);
if (jitsiRoom.getExpires() != null) {
bodyBuilder.expirationTime(Date.from(jitsiRoom.getExpires()));
}
if (jitsiRoom.getStarts() != null) {
bodyBuilder.notBeforeTime(Date.from(jitsiRoom.getStarts()));
}
JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT)
.build();
SignedJWT token = new SignedJWT(jwsHeader, bodyBuilder.build());
token.sign(signer);
jitsiRoom.setToken(token.serialize());
bodyBuilder.claim("moderator", true);
if (jitsiRoom.getModerationStarts() != null) {
bodyBuilder.notBeforeTime(Date.from(jitsiRoom.getModerationStarts()));
}
SignedJWT moderationToken = new SignedJWT(jwsHeader, bodyBuilder.build());
moderationToken.sign(signer);
jitsiRoom.setModerationToken(moderationToken.serialize());
jitsiRoom.setUrl(String.format(urlFormat, jitsiRoom.getRoom(), jitsiRoom.getToken()));
jitsiRoom.setModerationUrl(
String.format(urlFormat, jitsiRoom.getRoom(), jitsiRoom.getModerationToken()));
if (StringUtils.hasText(jitsiRoom.getCode())) {
ShortenedUrl shortenedUrl = shortenedUrlManager.get(jitsiRoom.getCode());
if (shortenedUrl != null) {
shortenedUrl.setUrl(jitsiRoom.getUrl());
shortenedUrl.setNote("JitsiRoom: " + jitsiRoom.getRoom());
shortenedUrl.setExpires(jitsiRoom.getExpires());
shortenedUrlManager.save(shortenedUrl);
jitsiRoom.setOrgUrl(jitsiRoom.getUrl());
jitsiRoom.setUrl(shortenedUrl.getLink());
} else {
jitsiRoom.setCode(null);
}
}
return jitsiRoomRepository.save(jitsiRoom);
}
/**
*
* @param jitsiRoom
* @return
*/
public JitsiRoom createShortenedUrl(JitsiRoom jitsiRoom) {
ShortenedUrl shortenedUrl = shortenedUrlManager.create(jitsiRoom.getOwner(),
jitsiRoom.getUrl(), "JitsiRoom: " + jitsiRoom.getRoom(), null,
jitsiRoom.getExpires(), null, false, true);
jitsiRoom.setOrgUrl(jitsiRoom.getUrl());
jitsiRoom.setCode(shortenedUrl.getCode());
jitsiRoom.setUrl(shortenedUrl.getLink());
return jitsiRoomRepository.save(jitsiRoom);
}
/**
*
* @param id
*/
public void delete(JitsiRoom jitsiRoom, boolean quota) {
if (quota) {
Quota jitsiRoomsQuota = quotaManager.get(jitsiRoom.getOwner(), JitsiQuotas.JITSI_MEET);
if (jitsiRoomsQuota == null) {
jitsiRoomsQuota = quotaManager.create(jitsiRoom.getOwner(), JitsiQuotas.JITSI_MEET,
0, "#", true);
}
jitsiRoomsQuota.setValue(jitsiRoomsQuota.getValue() + 1);
quotaManager.update(jitsiRoomsQuota);
}
if (StringUtils.hasText(jitsiRoom.getCode())) {
ShortenedUrl shortenedUrl = shortenedUrlRepository
.findOne(qShortenedUrl.code.eq(jitsiRoom.getCode())).orElse(null);
if (shortenedUrl != null) {
if (quota) {
Quota shortenedUrlsQuota = quotaManager.get(shortenedUrl.getOwner(),
ShortenedUrlQuotas.URL_SHORTENER);
if (shortenedUrlsQuota == null) {
shortenedUrlsQuota = quotaManager.create(shortenedUrl.getOwner(),
ShortenedUrlQuotas.URL_SHORTENER, 0, "#", true);
}
shortenedUrlsQuota.setValue(shortenedUrlsQuota.getValue() + 1);
quotaManager.update(shortenedUrlsQuota);
}
shortenedUrlRepository.delete(shortenedUrl);
}
}
jitsiRoomRepository.delete(jitsiRoom);
}
/**
*
* @param owner
* @param quota
*/
public void deleteAll(Long owner, boolean quota) {
List<JitsiRoom> jitsiRooms = Lists
.newArrayList(jitsiRoomRepository.findAll(qJitsiRoom.owner.eq(owner)));
for (JitsiRoom jitsiRoom : jitsiRooms) {
delete(jitsiRoom, quota);
}
}
/*
* @see de.bstly.we.businesslogic.UserDataProvider#getId()
*/
@Override
public String getId() {
return "jitsiRooms";
}
/*
* @see de.bstly.we.businesslogic.UserDataProvider#getUserData(java.lang.Long)
*/
@Override
public List<UserData> getUserData(Long userId) {
List<UserData> result = Lists.newArrayList();
for (JitsiRoom jitsiRoom : getAllByOwner(userId)) {
result.add(jitsiRoom);
}
return result;
}
/*
* @see de.bstly.we.businesslogic.UserDataProvider#purgeUserData(java.lang.Long)
*/
@Override
public void purgeUserData(Long userId) {
for (JitsiRoom jitsiRoom : getAllByOwner(userId)) {
jitsiRoomRepository.delete(jitsiRoom);
}
}
/**
*
*/
@Scheduled(cron = "0 */5 * * * *")
protected void clearExpiredRooms() {
jitsiRoomRepository
.deleteAll(jitsiRoomRepository.findAll(qJitsiRoom.expires.before(Instant.now())));
}
}
@@ -0,0 +1,62 @@
/**
*
*/
package de.bstly.we.jitsi.businesslogic.support;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import de.bstly.we.event.AbstractModelEvent;
import de.bstly.we.event.AbstractModelEventType;
import de.bstly.we.jitsi.model.JitsiRoom;
import de.bstly.we.jitsi.model.QJitsiRoom;
import de.bstly.we.jitsi.repository.JitsiRoomRepository;
import de.bstly.we.urlshortener.model.ShortenedUrl;
/**
* @author Lurkars
*
*/
@Component
public class UrlShortenerSync implements ApplicationListener<AbstractModelEvent> {
@Autowired
private JitsiRoomRepository jitsiRoomRepository;
private QJitsiRoom qJitsiRoom = QJitsiRoom.jitsiRoom;
/*
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.
* springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(AbstractModelEvent event) {
if (event.getModel() instanceof ShortenedUrl) {
if (AbstractModelEventType.PRE_REMOVE.equals(event.getType())) {
ShortenedUrl shortenedUrl = (ShortenedUrl) event.getModel();
JitsiRoom jitsiRoom = jitsiRoomRepository
.findOne(qJitsiRoom.code.eq(shortenedUrl.getCode())).orElse(null);
if (jitsiRoom != null) {
jitsiRoom.setUrl(jitsiRoom.getOrgUrl());
jitsiRoom.setCode(null);
jitsiRoom.setOrgUrl(null);
jitsiRoomRepository.save(jitsiRoom);
}
} else if (AbstractModelEventType.POST_UPDATE.equals(event.getType())) {
ShortenedUrl shortenedUrl = (ShortenedUrl) event.getModel();
JitsiRoom jitsiRoom = jitsiRoomRepository
.findOne(qJitsiRoom.code.eq(shortenedUrl.getCode())).orElse(null);
if (jitsiRoom != null) {
if (!StringUtils.hasText(jitsiRoom.getOrgUrl())) {
jitsiRoom.setOrgUrl(jitsiRoom.getUrl());
}
jitsiRoom.setUrl(shortenedUrl.getLink());
jitsiRoomRepository.save(jitsiRoom);
}
}
}
}
}
@@ -0,0 +1,212 @@
/**
*
*/
package de.bstly.we.jitsi.controller;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
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.nimbusds.jose.JOSEException;
import de.bstly.we.businesslogic.PermissionManager;
import de.bstly.we.businesslogic.Permissions;
import de.bstly.we.businesslogic.QuotaManager;
import de.bstly.we.controller.BaseController;
import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.controller.support.RequestBodyErrors;
import de.bstly.we.jitsi.businesslogic.JitsiPermissions;
import de.bstly.we.jitsi.businesslogic.JitsiQuotas;
import de.bstly.we.jitsi.businesslogic.JitsiRoomManager;
import de.bstly.we.jitsi.controller.validation.JitsiRoomValidator;
import de.bstly.we.jitsi.model.JitsiRoom;
import de.bstly.we.model.Quota;
import de.bstly.we.urlshortener.businesslogic.ShortenedUrlPermissions;
import de.bstly.we.urlshortener.businesslogic.ShortenedUrlQuotas;
/**
* @author _bastler@bstly.de
*
*/
@RestController
@RequestMapping("/jitsi/rooms")
public class JitsiRoomController extends BaseController {
@Autowired
private JitsiRoomManager jitsiRoomManager;
@Autowired
private JitsiRoomValidator jitsiRoomValidator;
@Autowired
private PermissionManager permissionManager;
@Autowired
private QuotaManager quotaManager;
/**
*
* @param pageParameter
* @param sizeParameter
* @return
*/
@PreAuthorize("isAuthenticated()")
@GetMapping
public Page<JitsiRoom> getJitsiRooms(@RequestParam("page") Optional<Integer> pageParameter,
@RequestParam("size") Optional<Integer> sizeParameter,
@RequestParam("sort") Optional<String> sortParameter,
@RequestParam("desc") Optional<Boolean> descParameter) {
if (!permissionManager.hasPermission(getCurrentUserId(), JitsiPermissions.JITSI_MEET)) {
jitsiRoomManager.deleteAll(getCurrentUserId(), false);
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
return jitsiRoomManager.getForUserId(getCurrentUserId(), pageParameter.orElse(0),
sizeParameter.orElse(10), sortParameter.orElse("id"), descParameter.orElse(false));
}
/**
*
* @param jitsiRoom
* @return
*/
@PreAuthorize("isAuthenticated()")
@PostMapping
public JitsiRoom createJitsiRoom(@RequestBody JitsiRoom jitsiRoom) {
if (!permissionManager.hasPermission(getCurrentUserId(), JitsiPermissions.JITSI_MEET)
|| !permissionManager.isFullUser(getCurrentUserId())) {
jitsiRoomManager.deleteAll(getCurrentUserId(), false);
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
Quota jitsiRoomsQuota = quotaManager.get(getCurrentUserId(), JitsiQuotas.JITSI_MEET);
if (jitsiRoomsQuota == null || jitsiRoomsQuota.getValue() < 1) {
throw new EntityResponseStatusException(HttpStatus.CONFLICT);
}
jitsiRoom.setOwner(getCurrentUserId());
Errors errors = new RequestBodyErrors(jitsiRoom);
if (permissionManager.hasPermission(getCurrentUserId(), Permissions.ROLE_ADMIN)) {
jitsiRoomValidator.validate(jitsiRoom.getOwner(), jitsiRoom.getRoom(), errors);
} else {
jitsiRoomValidator.validate(jitsiRoom, errors);
}
if (errors.hasErrors()) {
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
}
jitsiRoomsQuota.setValue(jitsiRoomsQuota.getValue() - 1);
jitsiRoomsQuota = quotaManager.update(jitsiRoomsQuota);
try {
return jitsiRoomManager.create(jitsiRoom.getOwner(), jitsiRoom.getRoom(),
jitsiRoom.getStarts(), jitsiRoom.getModerationStarts(), jitsiRoom.getExpires());
} catch (JOSEException e) {
jitsiRoomsQuota.setValue(jitsiRoomsQuota.getValue() - 1);
jitsiRoomsQuota = quotaManager.update(jitsiRoomsQuota);
throw new EntityResponseStatusException(HttpStatus.EXPECTATION_FAILED);
}
}
/**
*
* @param jitsiRoom
* @return
*/
@PreAuthorize("isAuthenticated()")
@PatchMapping
public JitsiRoom updateJitsiRoom(@RequestBody JitsiRoom jitsiRoom) {
if (!permissionManager.hasPermission(getCurrentUserId(), JitsiPermissions.JITSI_MEET)
|| !permissionManager.isFullUser(getCurrentUserId())) {
jitsiRoomManager.deleteAll(getCurrentUserId(), false);
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
if (jitsiRoom.getId() == null) {
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
JitsiRoom origJitsiRoom = jitsiRoomManager.get(jitsiRoom.getId());
if (origJitsiRoom == null || !origJitsiRoom.getOwner().equals(getCurrentUserId())) {
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
jitsiRoom.setOwner(getCurrentUserId());
Errors errors = new RequestBodyErrors(jitsiRoom);
if (permissionManager.hasPermission(getCurrentUserId(), Permissions.ROLE_ADMIN)) {
jitsiRoomValidator.validate(jitsiRoom.getOwner(), jitsiRoom.getRoom(), errors);
} else {
jitsiRoomValidator.validate(jitsiRoom, errors);
}
if (errors.hasErrors()) {
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
}
try {
return jitsiRoomManager.save(jitsiRoom);
} catch (JOSEException e) {
throw new EntityResponseStatusException(HttpStatus.EXPECTATION_FAILED);
}
}
@PreAuthorize("isAuthenticated()")
@PatchMapping("/{id}")
public JitsiRoom createShortenedUrl(@PathVariable("id") Long id) {
JitsiRoom jitsiRoom = jitsiRoomManager.get(id);
if (jitsiRoom == null || !(jitsiRoom.getOwner().equals(getCurrentUserId()))) {
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
if (!permissionManager.hasPermission(getCurrentUserId(),
ShortenedUrlPermissions.URL_SHORTENER)
|| !permissionManager.isFullUser(getCurrentUserId())) {
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
Quota shortenedUrlsQuota = quotaManager.get(getCurrentUserId(),
ShortenedUrlQuotas.URL_SHORTENER);
if (shortenedUrlsQuota == null || shortenedUrlsQuota.getValue() < 1) {
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
// create shortened Url
return jitsiRoomManager.createShortenedUrl(jitsiRoom);
}
/**
*
* @param id
*/
@PreAuthorize("isAuthenticated()")
@DeleteMapping("/{id}")
public void deleteJitsiRoom(@PathVariable("id") Long id) {
if (!permissionManager.hasPermission(getCurrentUserId(), JitsiPermissions.JITSI_MEET)) {
jitsiRoomManager.deleteAll(getCurrentUserId(), false);
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
JitsiRoom jitsiRoom = jitsiRoomManager.get(id);
if (jitsiRoom == null || !jitsiRoom.getOwner().equals(getCurrentUserId())) {
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
}
jitsiRoomManager.delete(jitsiRoom, true);
}
}
@@ -0,0 +1,105 @@
/**
*
*/
package de.bstly.we.jitsi.controller;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
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.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.nimbusds.jose.JOSEException;
import de.bstly.we.controller.BaseController;
import de.bstly.we.controller.support.EntityResponseStatusException;
import de.bstly.we.controller.support.RequestBodyErrors;
import de.bstly.we.jitsi.businesslogic.JitsiRoomManager;
import de.bstly.we.jitsi.controller.validation.JitsiRoomValidator;
import de.bstly.we.jitsi.model.JitsiRoom;
/**
* @author _bastler@bstly.de
*
*/
@RestController
@RequestMapping("/jitsi/rooms/manage")
public class JitsiRoomManagementController extends BaseController {
@Autowired
private JitsiRoomManager jitsiRoomManager;
@Autowired
private JitsiRoomValidator jitsiRoomValidator;
/**
*
* @return
*/
@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping
public Page<JitsiRoom> getJitsiRooms(@RequestParam("page") Optional<Integer> pageParameter,
@RequestParam("size") Optional<Integer> sizeParameter) {
return jitsiRoomManager.get(pageParameter.orElse(0), sizeParameter.orElse(10), "id", true);
}
/**
*
* @param jitsiRoom
* @return
*/
@PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping
public JitsiRoom createOrUpdateJitsiRoom(@RequestBody JitsiRoom jitsiRoom) {
Errors errors = new RequestBodyErrors(jitsiRoom);
jitsiRoomValidator.validate(jitsiRoom.getOwner(), jitsiRoom.getRoom(), errors);
if (errors.hasErrors()) {
throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT);
}
try {
return jitsiRoomManager.save(jitsiRoom);
} catch (JOSEException e) {
e.printStackTrace();
return null;
}
}
/**
*
* @param id
*/
@PreAuthorize("hasRole('ROLE_ADMIN')")
@DeleteMapping("/{id}")
public void deleteJitsiRoom(@PathVariable("id") Long id,
@RequestParam("quota") Optional<Boolean> quota) {
JitsiRoom jitsiRoom = jitsiRoomManager.get(id);
if (jitsiRoom == null) {
throw new EntityResponseStatusException(HttpStatus.CONFLICT);
}
jitsiRoomManager.delete(jitsiRoom, quota.isPresent() && quota.get().booleanValue());
}
/**
*
* @param owner
*/
@PreAuthorize("hasRole('ROLE_ADMIN')")
@DeleteMapping("/all/{owner}")
public void deleteAll(@PathVariable("owner") Long owner,
@RequestParam("quota") Optional<Boolean> quota) {
jitsiRoomManager.deleteAll(owner, quota.isPresent() && quota.get().booleanValue());
}
}
@@ -0,0 +1,189 @@
/**
*
*/
package de.bstly.we.jitsi.controller.validation;
import java.time.Instant;
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.bstly.we.businesslogic.SystemPropertyManager;
import de.bstly.we.businesslogic.UserManager;
import de.bstly.we.jitsi.model.JitsiRoom;
import de.bstly.we.jitsi.model.QJitsiRoom;
import de.bstly.we.jitsi.repository.JitsiRoomRepository;
/**
* @author _bastler@bstly.de
*
*/
@Component
public class JitsiRoomValidator implements Validator {
public static final String RESERVED_JITSI_ROOMS = "jitsi.rooms.reserved";
protected static final String ROOM_NAME_REGEX = "^([a-zA-Z0-9]+)$";
@Autowired
private UserManager userManager;
@Autowired
private SystemPropertyManager systemPropertyManager;
@Autowired
private JitsiRoomRepository jitsiRoomRepository;
private QJitsiRoom qJitsiRoom = QJitsiRoom.jitsiRoom;
/*
* @see org.springframework.validation.Validator#supports(java.lang.Class)
*/
@Override
public boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(JitsiRoom.class);
}
/*
* @see org.springframework.validation.Validator#validate(java.lang.Object,
* org.springframework.validation.Errors)
*/
@Override
public void validate(Object target, Errors errors) {
JitsiRoom jitsiRoom = (JitsiRoom) target;
validate(jitsiRoom.getOwner(), jitsiRoom.getRoom(), errors);
if (errors.hasErrors()) {
return;
}
validateRoom(jitsiRoom.getRoom(), errors);
validateExpiry(jitsiRoom, errors);
}
/**
*
* @param roomName
* @param field
* @param errors
*/
public void validate(Long owner, String roomName, Errors errors) {
if (owner == null) {
errors.rejectValue("owner", "REQUIRED");
return;
}
if (userManager.get(owner) == null) {
errors.rejectValue("owner", "NOT_VALID");
return;
}
if (!StringUtils.hasText(roomName)) {
errors.rejectValue("room", "REQUIRED");
return;
}
if (!roomName.matches(ROOM_NAME_REGEX)) {
errors.rejectValue("room", "NOT_VALID");
return;
}
}
/**
*
* @param owner
* @param roomName
* @param errors
*/
public void validateRoom(String roomName, Errors errors) {
for (String systemRoomName : systemPropertyManager.get(RESERVED_JITSI_ROOMS, "")
.split(",")) {
if (StringUtils.hasText(systemRoomName)
&& (roomName.toLowerCase().equals(systemRoomName)
|| roomName.toLowerCase().matches(systemRoomName))) {
errors.rejectValue("room", "NOT_VALID");
return;
}
}
}
/**
*
* @param jitsiRoom
* @param errors
*/
public void validateExpiry(JitsiRoom jitsiRoom, Errors errors) {
// no moderation start without start
if (jitsiRoom.getStarts() == null && jitsiRoom.getModerationStarts() != null) {
errors.rejectValue("moderationStarts", "NOT_VALID");
return;
} else
// no moderation before start
if (jitsiRoom.getStarts() != null && jitsiRoom.getModerationStarts() != null
&& jitsiRoom.getStarts().isBefore(jitsiRoom.getModerationStarts())) {
errors.rejectValue("moderationStarts", "NOT_VALID");
return;
}
// no expiry + no start
if (jitsiRoom.getExpires() == null && jitsiRoom.getStarts() == null) {
if (jitsiRoomRepository.exists(qJitsiRoom.room.eq(jitsiRoom.getRoom())
// exlude self
.and(qJitsiRoom.id.ne(jitsiRoom.getId() == null ? -1L : jitsiRoom.getId()))
// expires null or after now
.and(qJitsiRoom.expires.isNull()
.or(qJitsiRoom.expires.after(Instant.now()))))) {
errors.rejectValue("room", "NOT_VALID");
}
} else
// expiry + no start
if (jitsiRoom.getExpires() != null && jitsiRoom.getStarts() == null) {
if (jitsiRoomRepository.exists(qJitsiRoom.room.eq(jitsiRoom.getRoom())
// exlude self
.and(qJitsiRoom.id.ne(jitsiRoom.getId() == null ? -1L : jitsiRoom.getId()))
// expires null or after now
.and(qJitsiRoom.expires.isNull().or(qJitsiRoom.expires.after(Instant.now())))
// start null or before expires
.and(qJitsiRoom.starts.isNull()
.or(qJitsiRoom.starts.before(jitsiRoom.getExpires()))))) {
errors.rejectValue("expires", "NOT_VALID");
}
}
// no expiry + start
if (jitsiRoom.getExpires() == null && jitsiRoom.getStarts() != null) {
if (jitsiRoomRepository.exists(qJitsiRoom.room.eq(jitsiRoom.getRoom())
// exlude self
.and(qJitsiRoom.id.ne(jitsiRoom.getId() == null ? -1L : jitsiRoom.getId()))
// expires null or after room start
.and(qJitsiRoom.expires.isNull()
.or(qJitsiRoom.expires.after(jitsiRoom.getModerationStarts() != null
? jitsiRoom.getModerationStarts()
: jitsiRoom.getStarts()))))) {
errors.rejectValue("starts", "NOT_VALID");
}
} else
// expiry + start
if (jitsiRoom.getExpires() != null && jitsiRoom.getStarts() != null) {
if (jitsiRoom.getStarts().isAfter(jitsiRoom.getExpires())
|| jitsiRoomRepository.exists(qJitsiRoom.room.eq(jitsiRoom.getRoom())
// exlude self
.and(qJitsiRoom.id
.ne(jitsiRoom.getId() == null ? -1L : jitsiRoom.getId()))
// expires null or after room start
.and(qJitsiRoom.expires.isNull().or(
qJitsiRoom.expires.after(jitsiRoom.getModerationStarts() != null
? jitsiRoom.getModerationStarts()
: jitsiRoom.getStarts())))
// start null or before expires
.and(qJitsiRoom.starts.isNull()
.or(qJitsiRoom.starts.before(jitsiRoom.getExpires()))))) {
errors.rejectValue("starts", "NOT_VALID");
errors.rejectValue("expires", "NOT_VALID");
}
}
}
}
@@ -0,0 +1,221 @@
/**
*
*/
package de.bstly.we.jitsi.model;
import java.time.Instant;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import org.springframework.data.annotation.Reference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import de.bstly.we.model.UserData;
/**
* @author _bastler@bstly.de
*
*/
@Entity
@Table(name = "jitsi_rooms")
public class JitsiRoom implements UserData {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
private Long owner;
private String room;
private Instant starts;
private Instant moderationStarts;
private Instant expires;
@Lob
@JsonIgnore
private String token;
@Lob
@JsonIgnore
private String moderationToken;
@Lob
private String url;
@Lob
private String moderationUrl;
@Reference
private String code;
@Lob
private String orgUrl;
/**
* @return the id
*/
public Long getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(Long id) {
this.id = id;
}
/**
* @return the owner
*/
public Long getOwner() {
return owner;
}
/**
* @param owner the owner to set
*/
public void setOwner(Long owner) {
this.owner = owner;
}
/**
* @return the room
*/
public String getRoom() {
return room;
}
/**
* @param room the room to set
*/
public void setRoom(String room) {
this.room = room;
}
/**
* @return the starts
*/
public Instant getStarts() {
return starts;
}
/**
* @param starts the starts to set
*/
public void setStarts(Instant starts) {
this.starts = starts;
}
/**
* @return the moderationStarts
*/
public Instant getModerationStarts() {
return moderationStarts;
}
/**
* @param moderationStarts the moderationStarts to set
*/
public void setModerationStarts(Instant moderationStarts) {
this.moderationStarts = moderationStarts;
}
/**
* @return the expires
*/
public Instant getExpires() {
return expires;
}
/**
* @param expires the expires to set
*/
public void setExpires(Instant expires) {
this.expires = expires;
}
/**
* @return the token
*/
public String getToken() {
return token;
}
/**
* @param token the token to set
*/
public void setToken(String token) {
this.token = token;
}
/**
* @return the moderationToken
*/
public String getModerationToken() {
return moderationToken;
}
/**
* @param moderationToken the moderationToken to set
*/
public void setModerationToken(String moderationToken) {
this.moderationToken = moderationToken;
}
/**
* @return the url
*/
public String getUrl() {
return url;
}
/**
* @param url the url to set
*/
public void setUrl(String url) {
this.url = url;
}
/**
* @return the moderationUrl
*/
public String getModerationUrl() {
return moderationUrl;
}
/**
* @param moderationUrl the moderationUrl to set
*/
public void setModerationUrl(String moderationUrl) {
this.moderationUrl = moderationUrl;
}
/**
* @return the code
*/
public String getCode() {
return code;
}
/**
* @param code the code to set
*/
public void setCode(String code) {
this.code = code;
}
/**
* @return the orgUrl
*/
public String getOrgUrl() {
return orgUrl;
}
/**
* @param orgUrl the orgUrl to set
*/
public void setOrgUrl(String orgUrl) {
this.orgUrl = orgUrl;
}
}
@@ -0,0 +1,20 @@
/**
*
*/
package de.bstly.we.jitsi.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository;
import de.bstly.we.jitsi.model.JitsiRoom;
/**
*
* @author _bastler@bstly.de
*
*/
@Repository
public interface JitsiRoomRepository
extends JpaRepository<JitsiRoom, Long>, QuerydslPredicateExecutor<JitsiRoom> {
}