diff --git a/.run/Run Cucumber Tests.run.xml b/.run/Run Cucumber Tests.run.xml new file mode 100644 index 00000000..c7ced12e --- /dev/null +++ b/.run/Run Cucumber Tests.run.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/java/de/filefighter/rest/configuration/PrepareDataBase.java b/src/main/java/de/filefighter/rest/configuration/PrepareDataBase.java index f68d0571..44529719 100644 --- a/src/main/java/de/filefighter/rest/configuration/PrepareDataBase.java +++ b/src/main/java/de/filefighter/rest/configuration/PrepareDataBase.java @@ -1,21 +1,53 @@ package de.filefighter.rest.configuration; import de.filefighter.rest.domain.filesystem.data.persistance.FileSystemRepository; +import de.filefighter.rest.domain.token.business.AccessTokenBusinessService; +import de.filefighter.rest.domain.token.data.persistance.AccessTokenEntity; import de.filefighter.rest.domain.token.data.persistance.AccessTokenRepository; import de.filefighter.rest.domain.user.data.persistance.UserEntity; import de.filefighter.rest.domain.user.data.persistance.UserRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import java.time.Instant; + @Configuration public class PrepareDataBase { + @Value("${server.port}") + int serverPort; + private static final Logger LOG = LoggerFactory.getLogger(PrepareDataBase.class); + @Bean + @Profile({"dev", "prod"}) + CommandLineRunner veryImportantFileFighterStartScript() { + return args -> { + System.out.println(); + System.out.println("-------------------------------< REST API >-------------------------------"); + System.out.println(); + System.out.println(" _____ _ _ _____ _ _ _ "); + System.out.println(" | ___| (_) | | ___ | ___| (_) __ _ | |__ | |_ ___ _ __ "); + System.out.println(" | |_ | | | | / _ \\ | |_ | | / _ | | '_ \\ | __| / _ \\ | '__|"); + System.out.println(" | _| | | | | | __/ | _| | | | (_| | | | | | | |_ | __/ | | "); + System.out.println(" |_| |_| |_| \\___| |_| |_| \\__, | |_| |_| \\__| \\___| |_| "); + System.out.println(" |___/ "); + System.out.println(" Version 0.2 Last updated at 03.11.20 "); + System.out.println(" Developed by Gimleux, Valentin, Open-Schnick. "); + System.out.println(" Development Blog: https://filefighter.github.io "); + System.out.println(" The code can be found at: https://www.github.com/filefighter "); + System.out.println(" Running on http://localhost:" + serverPort); + System.out.println(); + System.out.println("-------------------------------< REST API >-------------------------------"); + System.out.println(); + }; + } + @Bean CommandLineRunner cleanDataBase(UserRepository userRepository, FileSystemRepository fileSystemRepository, AccessTokenRepository accessTokenRepository) { @@ -32,7 +64,7 @@ CommandLineRunner cleanDataBase(UserRepository userRepository, FileSystemReposit @Bean @Profile("prod") - CommandLineRunner initUserDataBase(UserRepository repository) { + CommandLineRunner initUserDataBaseProd(UserRepository repository) { //Note: when the admin user changes his/her password, a new refreshToken will be created. return args -> { @@ -42,9 +74,57 @@ CommandLineRunner initUserDataBase(UserRepository repository) { .username("admin") .password("admin") .refreshToken("refreshToken1234") - .roleIds(new long[]{0, 1}) + .groupIds(new long[]{0, 1}) .build())); - LOG.info("Loading Users" + (repository.findAll().size() == 1 ? " was successful." : " failed.")); + LOG.info("Inserting Users" + (repository.findAll().size() == 1 ? " was successful." : " failed.")); + }; + } + + @Bean + @Profile("dev") + CommandLineRunner initUserDataBaseDev(UserRepository repository) { + + return args -> { + LOG.info("Preloading default users: " + + repository.save(UserEntity + .builder() + .userId(0) + .username("user") + .password("1234") + .refreshToken("rft1234") + .groupIds(new long[]{0}) + .build()) + + repository.save(UserEntity + .builder() + .userId(1) + .username("user1") + .password("12345") + .refreshToken("rft") + .groupIds(new long[]{-1}) + .build())); + LOG.info("Inserting Users" + (repository.findAll().size() == 2 ? " was successful." : " failed.")); + }; + } + + @Bean + @Profile("dev") + CommandLineRunner initAccessTokenDataBaseDev(AccessTokenRepository repository) { + + return args -> { + LOG.info("Preloading default tokens: " + + repository.save(AccessTokenEntity + .builder() + .userId(0) + .value("token") + .validUntil(Instant.now().getEpochSecond() + AccessTokenBusinessService.ACCESS_TOKEN_DURATION_IN_SECONDS) + .build()) + + repository.save(AccessTokenEntity + .builder() + .userId(1) + .value("token1234") + .validUntil(Instant.now().getEpochSecond() + AccessTokenBusinessService.ACCESS_TOKEN_DURATION_IN_SECONDS) + .build())); + LOG.info("Inserting token" + (repository.findAll().size() == 2 ? " was successful." : " failed.")); }; } } \ No newline at end of file diff --git a/src/main/java/de/filefighter/rest/domain/common/DtoServiceInterface.java b/src/main/java/de/filefighter/rest/domain/common/DtoServiceInterface.java new file mode 100644 index 00000000..06605dbb --- /dev/null +++ b/src/main/java/de/filefighter/rest/domain/common/DtoServiceInterface.java @@ -0,0 +1,6 @@ +package de.filefighter.rest.domain.common; + +public interface DtoServiceInterface { + D createDto(E entity); + E findEntity(D dto); +} diff --git a/src/main/java/de/filefighter/rest/domain/common/Utils.java b/src/main/java/de/filefighter/rest/domain/common/Utils.java new file mode 100644 index 00000000..4e81e622 --- /dev/null +++ b/src/main/java/de/filefighter/rest/domain/common/Utils.java @@ -0,0 +1,8 @@ +package de.filefighter.rest.domain.common; + +public class Utils { + + public static boolean stringIsValid(String s){ + return !(null == s || s.isEmpty() || s.isBlank()); + } +} diff --git a/src/main/java/de/filefighter/rest/domain/filesystem/data/dto/FileSystemItemUpdate.java b/src/main/java/de/filefighter/rest/domain/filesystem/data/dto/FileSystemItemUpdate.java index 77bf6390..1e7a6d24 100644 --- a/src/main/java/de/filefighter/rest/domain/filesystem/data/dto/FileSystemItemUpdate.java +++ b/src/main/java/de/filefighter/rest/domain/filesystem/data/dto/FileSystemItemUpdate.java @@ -5,7 +5,7 @@ import lombok.Data; @Data -@Builder(builderMethodName = "create") +@Builder public class FileSystemItemUpdate { private String name; private FileSystemType type; diff --git a/src/main/java/de/filefighter/rest/domain/filesystem/data/dto/FolderContents.java b/src/main/java/de/filefighter/rest/domain/filesystem/data/dto/FolderContents.java index f144c5c2..153821da 100644 --- a/src/main/java/de/filefighter/rest/domain/filesystem/data/dto/FolderContents.java +++ b/src/main/java/de/filefighter/rest/domain/filesystem/data/dto/FolderContents.java @@ -4,7 +4,7 @@ import lombok.Getter; @Getter -@Builder(buildMethodName = "create") +@Builder public class FolderContents { private final Folder[] folders; private final File[] files; diff --git a/src/main/java/de/filefighter/rest/domain/filesystem/data/persistance/FileSystemEntity.java b/src/main/java/de/filefighter/rest/domain/filesystem/data/persistance/FileSystemEntity.java index 925edc29..febd46a2 100644 --- a/src/main/java/de/filefighter/rest/domain/filesystem/data/persistance/FileSystemEntity.java +++ b/src/main/java/de/filefighter/rest/domain/filesystem/data/persistance/FileSystemEntity.java @@ -7,7 +7,7 @@ @Data @Document(collection = "file") -@Builder(buildMethodName = "create") +@Builder public class FileSystemEntity { @MongoId private String _id; private long id; diff --git a/src/main/java/de/filefighter/rest/domain/health/business/SystemHealthBusinessService.java b/src/main/java/de/filefighter/rest/domain/health/business/SystemHealthBusinessService.java index 86721846..314bc04e 100644 --- a/src/main/java/de/filefighter/rest/domain/health/business/SystemHealthBusinessService.java +++ b/src/main/java/de/filefighter/rest/domain/health/business/SystemHealthBusinessService.java @@ -22,7 +22,7 @@ public SystemHealth getCurrentSystemHealthInfo(){ return SystemHealth.builder() .uptimeInSeconds(currentEpoch - serverStartedAt) .userCount(userBusinessService.getUserCount()) - .create(); + .build(); } public long getCurrentEpochSeconds(){ diff --git a/src/main/java/de/filefighter/rest/domain/health/data/SystemHealth.java b/src/main/java/de/filefighter/rest/domain/health/data/SystemHealth.java index ceff106c..e98ada97 100644 --- a/src/main/java/de/filefighter/rest/domain/health/data/SystemHealth.java +++ b/src/main/java/de/filefighter/rest/domain/health/data/SystemHealth.java @@ -8,7 +8,7 @@ */ @Getter -@Builder(buildMethodName = "create") +@Builder public class SystemHealth { private final long uptimeInSeconds; private final long userCount; diff --git a/src/main/java/de/filefighter/rest/domain/token/business/AccessTokenBusinessService.java b/src/main/java/de/filefighter/rest/domain/token/business/AccessTokenBusinessService.java index 18b9fc61..88135d5e 100644 --- a/src/main/java/de/filefighter/rest/domain/token/business/AccessTokenBusinessService.java +++ b/src/main/java/de/filefighter/rest/domain/token/business/AccessTokenBusinessService.java @@ -1,8 +1,80 @@ package de.filefighter.rest.domain.token.business; +import de.filefighter.rest.domain.token.data.dto.AccessToken; +import de.filefighter.rest.domain.token.data.persistance.AccessTokenEntity; +import de.filefighter.rest.domain.token.data.persistance.AccessTokenRepository; +import de.filefighter.rest.domain.token.exceptions.AccessTokenNotFoundException; +import de.filefighter.rest.domain.user.data.dto.User; +import de.filefighter.rest.domain.user.exceptions.UserNotAuthenticatedException; import org.springframework.stereotype.Service; +import java.time.Instant; +import java.util.UUID; + +import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BASIC_PREFIX; +import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BEARER_PREFIX; +import static de.filefighter.rest.domain.common.Utils.stringIsValid; + @Service public class AccessTokenBusinessService { + + private final AccessTokenRepository accessTokenRepository; + private final AccessTokenDtoService accessTokenDtoService; + public static final long ACCESS_TOKEN_DURATION_IN_SECONDS = 3600L; + public static final long ACCESS_TOKEN_SAFETY_MARGIN = 5L; + + public AccessTokenBusinessService(AccessTokenRepository accessTokenRepository, AccessTokenDtoService accessTokenDtoService) { + this.accessTokenRepository = accessTokenRepository; + this.accessTokenDtoService = accessTokenDtoService; + } + + public AccessToken getValidAccessTokenForUser(User user) { + AccessTokenEntity accessTokenEntity = accessTokenRepository.findByUserId(user.getId()); + long currentTimeSeconds = Instant.now().getEpochSecond(); + + if (null == accessTokenEntity) { + accessTokenEntity = AccessTokenEntity + .builder() + .validUntil(currentTimeSeconds + ACCESS_TOKEN_DURATION_IN_SECONDS) + .value(this.generateRandomTokenValue()) + .userId(user.getId()) + .build(); + accessTokenEntity = accessTokenRepository.save(accessTokenEntity); + } else { + if (currentTimeSeconds + ACCESS_TOKEN_SAFETY_MARGIN > accessTokenEntity.getValidUntil()) { + accessTokenRepository.delete(accessTokenEntity); + accessTokenEntity = AccessTokenEntity + .builder() + .validUntil(currentTimeSeconds + ACCESS_TOKEN_DURATION_IN_SECONDS) + .value(this.generateRandomTokenValue()) + .userId(user.getId()) + .build(); + accessTokenEntity = accessTokenRepository.save(accessTokenEntity); + } + } + + return accessTokenDtoService.createDto(accessTokenEntity); + } + + public AccessToken findAccessTokenByValueAndUserId(String accessTokenValue, long userId) { + if (!stringIsValid(accessTokenValue)) + throw new IllegalArgumentException("Value of AccessToken was not valid."); + + AccessTokenEntity accessTokenEntity = accessTokenRepository.findByUserIdAndValue(userId, accessTokenValue); + if (null == accessTokenEntity) + throw new UserNotAuthenticatedException(userId); + + return accessTokenDtoService.createDto(accessTokenEntity); + } + + public String generateRandomTokenValue() { + return UUID.randomUUID().toString(); + } + + public String checkBearerHeader(String accessTokenValue) { + if (!accessTokenValue.matches("^" + AUTHORIZATION_BEARER_PREFIX + "[^\\s](.*)$")) + throw new UserNotAuthenticatedException("Header does not contain '" + AUTHORIZATION_BEARER_PREFIX + "', or format is invalid."); + return accessTokenValue.split(AUTHORIZATION_BEARER_PREFIX)[1]; + } } diff --git a/src/main/java/de/filefighter/rest/domain/token/business/AccessTokenDtoService.java b/src/main/java/de/filefighter/rest/domain/token/business/AccessTokenDtoService.java new file mode 100644 index 00000000..f889297d --- /dev/null +++ b/src/main/java/de/filefighter/rest/domain/token/business/AccessTokenDtoService.java @@ -0,0 +1,37 @@ +package de.filefighter.rest.domain.token.business; + +import de.filefighter.rest.domain.common.DtoServiceInterface; +import de.filefighter.rest.domain.token.data.dto.AccessToken; +import de.filefighter.rest.domain.token.data.persistance.AccessTokenEntity; +import de.filefighter.rest.domain.token.data.persistance.AccessTokenRepository; +import de.filefighter.rest.domain.token.exceptions.AccessTokenNotFoundException; +import org.springframework.stereotype.Service; + +@Service +public class AccessTokenDtoService implements DtoServiceInterface { + + private final AccessTokenRepository accessTokenRepository; + + public AccessTokenDtoService(AccessTokenRepository accessTokenRepository) { + this.accessTokenRepository = accessTokenRepository; + } + + @Override + public AccessToken createDto(AccessTokenEntity entity) { + return AccessToken + .builder() + .token(entity.getValue()) + .userId(entity.getUserId()) + .validUntil(entity.getValidUntil()) + .build(); + } + + @Override + public AccessTokenEntity findEntity(AccessToken dto) { + AccessTokenEntity accessTokenEntity = accessTokenRepository.findByUserIdAndValue(dto.getUserId(), dto.getToken()); + if (null == accessTokenEntity) + throw new AccessTokenNotFoundException("AccessTokenEntity does not exist for AccessToken: "+ dto); + + return accessTokenEntity; + } +} diff --git a/src/main/java/de/filefighter/rest/domain/token/data/persistance/AccessTokenEntity.java b/src/main/java/de/filefighter/rest/domain/token/data/persistance/AccessTokenEntity.java index 172ca570..3c7dea5a 100644 --- a/src/main/java/de/filefighter/rest/domain/token/data/persistance/AccessTokenEntity.java +++ b/src/main/java/de/filefighter/rest/domain/token/data/persistance/AccessTokenEntity.java @@ -15,5 +15,5 @@ public class AccessTokenEntity { private String value; private long userId; private long validUntil; - + } \ No newline at end of file diff --git a/src/main/java/de/filefighter/rest/domain/token/data/persistance/AccessTokenRepository.java b/src/main/java/de/filefighter/rest/domain/token/data/persistance/AccessTokenRepository.java index 4bf99bb7..92f49f4f 100644 --- a/src/main/java/de/filefighter/rest/domain/token/data/persistance/AccessTokenRepository.java +++ b/src/main/java/de/filefighter/rest/domain/token/data/persistance/AccessTokenRepository.java @@ -7,5 +7,5 @@ public interface AccessTokenRepository extends MongoRepository { AccessTokenEntity findByUserId(long userId); AccessTokenEntity findByValue(String value); - void deleteByUserId(long userId); + AccessTokenEntity findByUserIdAndValue(long userId, String value); } diff --git a/src/main/java/de/filefighter/rest/domain/token/exceptions/TokenNotFoundAdvise.java b/src/main/java/de/filefighter/rest/domain/token/exceptions/AccessTokenNotFoundAdvise.java similarity index 75% rename from src/main/java/de/filefighter/rest/domain/token/exceptions/TokenNotFoundAdvise.java rename to src/main/java/de/filefighter/rest/domain/token/exceptions/AccessTokenNotFoundAdvise.java index 443253fd..c5e3c583 100644 --- a/src/main/java/de/filefighter/rest/domain/token/exceptions/TokenNotFoundAdvise.java +++ b/src/main/java/de/filefighter/rest/domain/token/exceptions/AccessTokenNotFoundAdvise.java @@ -11,13 +11,13 @@ import org.springframework.web.bind.annotation.ResponseStatus; @ControllerAdvice -public class TokenNotFoundAdvise { +public class AccessTokenNotFoundAdvise { @ResponseBody - @ExceptionHandler(TokenNotFoundException.class) + @ExceptionHandler(AccessTokenNotFoundException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - ResponseEntity tokenNotFoundAdvise(TokenNotFoundException ex) { + ResponseEntity tokenNotFoundAdvise(AccessTokenNotFoundException ex) { LoggerFactory.getLogger(UserAlreadyExistsAdvise.class).warn(ex.getMessage()); - return new ResponseEntity<>(new ServerResponse("Denied", ex.getMessage()), HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(new ServerResponse("denied", ex.getMessage()), HttpStatus.BAD_REQUEST); } } diff --git a/src/main/java/de/filefighter/rest/domain/token/exceptions/AccessTokenNotFoundException.java b/src/main/java/de/filefighter/rest/domain/token/exceptions/AccessTokenNotFoundException.java new file mode 100644 index 00000000..adecc922 --- /dev/null +++ b/src/main/java/de/filefighter/rest/domain/token/exceptions/AccessTokenNotFoundException.java @@ -0,0 +1,8 @@ +package de.filefighter.rest.domain.token.exceptions; + +public class AccessTokenNotFoundException extends RuntimeException { + + public AccessTokenNotFoundException(String reason) { + super(reason); + } +} diff --git a/src/main/java/de/filefighter/rest/domain/token/exceptions/TokenNotFoundException.java b/src/main/java/de/filefighter/rest/domain/token/exceptions/TokenNotFoundException.java deleted file mode 100644 index 3437b430..00000000 --- a/src/main/java/de/filefighter/rest/domain/token/exceptions/TokenNotFoundException.java +++ /dev/null @@ -1,8 +0,0 @@ -package de.filefighter.rest.domain.token.exceptions; - -public class TokenNotFoundException extends RuntimeException { - - public TokenNotFoundException(String reason) { - super(reason); - } -} diff --git a/src/main/java/de/filefighter/rest/domain/user/business/UserBusinessService.java b/src/main/java/de/filefighter/rest/domain/user/business/UserBusinessService.java index 7d2851c0..538f84c0 100644 --- a/src/main/java/de/filefighter/rest/domain/user/business/UserBusinessService.java +++ b/src/main/java/de/filefighter/rest/domain/user/business/UserBusinessService.java @@ -1,17 +1,113 @@ package de.filefighter.rest.domain.user.business; +import de.filefighter.rest.domain.token.data.dto.AccessToken; +import de.filefighter.rest.domain.token.data.dto.RefreshToken; +import de.filefighter.rest.domain.user.data.dto.User; +import de.filefighter.rest.domain.user.data.persistance.UserEntity; import de.filefighter.rest.domain.user.data.persistance.UserRepository; +import de.filefighter.rest.domain.user.exceptions.UserNotAuthenticatedException; +import de.filefighter.rest.domain.user.exceptions.UserNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.sql.Ref; +import java.util.Base64; +import java.util.UUID; + +import static de.filefighter.rest.configuration.RestConfiguration.*; +import static de.filefighter.rest.domain.common.Utils.stringIsValid; + @Service public class UserBusinessService { + private final UserRepository userRepository; + private final UserDtoService userDtoService; + + private static final Logger LOG = LoggerFactory.getLogger(UserBusinessService.class); - public UserBusinessService(UserRepository userRepository) { + public UserBusinessService(UserRepository userRepository, UserDtoService userDtoService) { this.userRepository = userRepository; + this.userDtoService = userDtoService; } - public long getUserCount(){ + public long getUserCount() { return userRepository.count(); } + + public User getUserByUsernameAndPassword(String base64encodedUserAndPasswordWithHeaderPrefix) { + if (!stringIsValid(base64encodedUserAndPasswordWithHeaderPrefix)) + throw new UserNotAuthenticatedException("Header was empty."); + + //TODO: maybe filter unsupported characters? + if (!base64encodedUserAndPasswordWithHeaderPrefix.matches("^" + AUTHORIZATION_BASIC_PREFIX + "[^\\s](.*)$")) + throw new UserNotAuthenticatedException("Header does not contain '" + AUTHORIZATION_BASIC_PREFIX + "', or format is invalid."); + + String[] split = base64encodedUserAndPasswordWithHeaderPrefix.split(AUTHORIZATION_BASIC_PREFIX); + + base64encodedUserAndPasswordWithHeaderPrefix = split[1]; + String decodedUsernameUndPassword; + try { + byte[] decodedValue = Base64.getDecoder().decode(base64encodedUserAndPasswordWithHeaderPrefix); + decodedUsernameUndPassword = new String(decodedValue, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException | IllegalArgumentException ex) { + LOG.warn("Found UnsupportedEncodingException in {}", base64encodedUserAndPasswordWithHeaderPrefix); + throw new RuntimeException(ex); + } + + split = decodedUsernameUndPassword.strip().split(":"); + + if (split.length != 2) + throw new UserNotAuthenticatedException("Credentials didnt meet formal requirements."); + + String username = split[0]; + String password = split[1]; + + UserEntity userEntity = userRepository.findByUsernameAndPassword(username, password); + if (null == userEntity) + throw new UserNotFoundException("No User found with this username and password."); + + return userDtoService.createDto(userEntity); + } + + public RefreshToken getRefreshTokenForUser(User user) { + UserEntity userEntity = userRepository.findByUserIdAndUsername(user.getId(), user.getUsername()); + if (null == userEntity) + throw new UserNotFoundException(); + + String refreshTokenValue = userEntity.getRefreshToken(); + + if (!stringIsValid(refreshTokenValue)) + throw new IllegalStateException("RefreshToken was empty in db."); + + return RefreshToken + .builder() + .refreshToken(refreshTokenValue) + .user(user) + .build(); + } + + public User getUserByRefreshTokenAndUserId(String refreshToken, long userId) { + if (!stringIsValid(refreshToken)) + throw new UserNotAuthenticatedException("RefreshToken was not valid."); + + UserEntity userEntity = userRepository.findByRefreshTokenAndUserId(refreshToken, userId); + if (null == userEntity) + throw new UserNotFoundException(userId); + + return userDtoService.createDto(userEntity); + } + + public User getUserByAccessTokenAndUserId(AccessToken accessToken, long userId) { + if (accessToken.getUserId() != userId) + throw new UserNotAuthenticatedException(userId); + + UserEntity userEntity = userRepository.findByUserId(userId); + if (null == userEntity) + throw new UserNotFoundException(userId); + + return userDtoService.createDto(userEntity); + } } diff --git a/src/main/java/de/filefighter/rest/domain/user/business/UserDtoService.java b/src/main/java/de/filefighter/rest/domain/user/business/UserDtoService.java new file mode 100644 index 00000000..ccbebf39 --- /dev/null +++ b/src/main/java/de/filefighter/rest/domain/user/business/UserDtoService.java @@ -0,0 +1,40 @@ +package de.filefighter.rest.domain.user.business; + +import de.filefighter.rest.domain.common.DtoServiceInterface; +import de.filefighter.rest.domain.user.data.dto.User; +import de.filefighter.rest.domain.user.data.persistance.UserEntity; +import de.filefighter.rest.domain.user.data.persistance.UserRepository; +import de.filefighter.rest.domain.user.exceptions.UserNotFoundException; +import de.filefighter.rest.domain.user.role.GroupRepository; +import org.springframework.stereotype.Service; + +@Service +public class UserDtoService implements DtoServiceInterface { + + private final GroupRepository groupRepository; + private final UserRepository userRepository; + + public UserDtoService(GroupRepository groupRepository, UserRepository userRepository) { + this.groupRepository = groupRepository; + this.userRepository = userRepository; + } + + @Override + public User createDto(UserEntity entity) { + return User + .builder() + .id(entity.getUserId()) + .username(entity.getUsername()) + .groups(groupRepository.getRolesByIds(entity.getGroupIds())) + .build(); + } + + @Override + public UserEntity findEntity(User dto) { + UserEntity userEntity = userRepository.findByUserIdAndUsername(dto.getId(), dto.getUsername()); + if (null == userEntity) + throw new UserNotFoundException(dto.getId()); + + return userEntity; + } +} diff --git a/src/main/java/de/filefighter/rest/domain/user/data/dto/User.java b/src/main/java/de/filefighter/rest/domain/user/data/dto/User.java index b1c5bb80..d70e6bb0 100644 --- a/src/main/java/de/filefighter/rest/domain/user/data/dto/User.java +++ b/src/main/java/de/filefighter/rest/domain/user/data/dto/User.java @@ -4,16 +4,16 @@ import lombok.Data; -@Builder(builderClassName = "UserBuilder", buildMethodName = "create") @Data +@Builder public class User { private long id; private String username; - private Groups[] roles; + private Groups[] groups; public User(long id, String username, Groups... roles) { this.id = id; this.username = username; - this.roles = roles; + this.groups = roles; } } \ No newline at end of file diff --git a/src/main/java/de/filefighter/rest/domain/user/data/dto/UserRegisterForm.java b/src/main/java/de/filefighter/rest/domain/user/data/dto/UserRegisterForm.java index e36029c6..af076c41 100644 --- a/src/main/java/de/filefighter/rest/domain/user/data/dto/UserRegisterForm.java +++ b/src/main/java/de/filefighter/rest/domain/user/data/dto/UserRegisterForm.java @@ -4,7 +4,7 @@ import lombok.Data; @Data -@Builder(buildMethodName = "create", builderClassName = "UserRegistrationFormBuilder") +@Builder public class UserRegisterForm { private String username; private String password; diff --git a/src/main/java/de/filefighter/rest/domain/user/data/persistance/UserEntity.java b/src/main/java/de/filefighter/rest/domain/user/data/persistance/UserEntity.java index db28695c..a4604180 100644 --- a/src/main/java/de/filefighter/rest/domain/user/data/persistance/UserEntity.java +++ b/src/main/java/de/filefighter/rest/domain/user/data/persistance/UserEntity.java @@ -16,7 +16,6 @@ public class UserEntity { private String username; private String password; private String refreshToken; //TODO: add valid_until for refreshToken - private long[] roleIds; - + private long[] groupIds; } diff --git a/src/main/java/de/filefighter/rest/domain/user/data/persistance/UserRepository.java b/src/main/java/de/filefighter/rest/domain/user/data/persistance/UserRepository.java index aafc05be..27d51680 100644 --- a/src/main/java/de/filefighter/rest/domain/user/data/persistance/UserRepository.java +++ b/src/main/java/de/filefighter/rest/domain/user/data/persistance/UserRepository.java @@ -5,7 +5,8 @@ @Service public interface UserRepository extends MongoRepository { - UserEntity findByUserId(long userId); + UserEntity findByUserIdAndUsername(long userId, String username); UserEntity findByUsernameAndPassword(String username, String password); - UserEntity findByRefreshToken(String refreshToken); + UserEntity findByRefreshTokenAndUserId(String refreshToken, long userId); + UserEntity findByUserId(long userId); } diff --git a/src/main/java/de/filefighter/rest/domain/user/exceptions/UserAlreadyExistsAdvise.java b/src/main/java/de/filefighter/rest/domain/user/exceptions/UserAlreadyExistsAdvise.java index e5d89cb6..bd9cfbde 100644 --- a/src/main/java/de/filefighter/rest/domain/user/exceptions/UserAlreadyExistsAdvise.java +++ b/src/main/java/de/filefighter/rest/domain/user/exceptions/UserAlreadyExistsAdvise.java @@ -17,6 +17,6 @@ public class UserAlreadyExistsAdvise { ResponseEntity userAlreadyExistsAdvise(UserAlreadyExistsException ex) { LoggerFactory.getLogger(UserAlreadyExistsAdvise.class).warn(ex.getMessage()); - return new ResponseEntity<>(new ServerResponse("Denied", ex.getMessage()), HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(new ServerResponse("denied", ex.getMessage()), HttpStatus.BAD_REQUEST); } } diff --git a/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotAuthenticatedAdvise.java b/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotAuthenticatedAdvise.java index 8f65bb53..3fb11dbb 100644 --- a/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotAuthenticatedAdvise.java +++ b/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotAuthenticatedAdvise.java @@ -17,6 +17,6 @@ public class UserNotAuthenticatedAdvise { @ResponseStatus(HttpStatus.UNAUTHORIZED) ResponseEntity userNotAuthenticatedHandler(UserNotAuthenticatedException ex) { LoggerFactory.getLogger(UserAlreadyExistsAdvise.class).warn(ex.getMessage()); - return new ResponseEntity<>(new ServerResponse("Denied", ex.getMessage()), HttpStatus.UNAUTHORIZED); + return new ResponseEntity<>(new ServerResponse("denied", ex.getMessage()), HttpStatus.UNAUTHORIZED); } } \ No newline at end of file diff --git a/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotAuthenticatedException.java b/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotAuthenticatedException.java index 68cc6f85..7b31ae1e 100644 --- a/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotAuthenticatedException.java +++ b/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotAuthenticatedException.java @@ -5,9 +5,9 @@ public UserNotAuthenticatedException(String reason){ super("User could not be authenticated. "+reason); } - public UserNotAuthenticatedException() { - super("User could not be authenticated."); - } +// public UserNotAuthenticatedException() { +// super("User could not be authenticated."); +// } public UserNotAuthenticatedException(long id){ super("User with the id "+id+" could not be authenticated."); diff --git a/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotFoundAdvice.java b/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotFoundAdvice.java index bf255f31..3df30506 100644 --- a/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotFoundAdvice.java +++ b/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotFoundAdvice.java @@ -17,6 +17,6 @@ class UserNotFoundAdvice { @ResponseStatus(HttpStatus.NOT_FOUND) ResponseEntity userNotFoundHandler(UserNotFoundException ex) { LoggerFactory.getLogger(UserAlreadyExistsAdvise.class).warn(ex.getMessage()); - return new ResponseEntity<>(new ServerResponse("Denied", ex.getMessage()), HttpStatus.NOT_FOUND); + return new ResponseEntity<>(new ServerResponse("denied", ex.getMessage()), HttpStatus.NOT_FOUND); } } diff --git a/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotFoundException.java b/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotFoundException.java index 463b00d8..9c89d4d8 100644 --- a/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotFoundException.java +++ b/src/main/java/de/filefighter/rest/domain/user/exceptions/UserNotFoundException.java @@ -2,6 +2,10 @@ public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(){ + super("User not found."); + } + public UserNotFoundException(long id) { super("Could not find user " + id); } diff --git a/src/main/java/de/filefighter/rest/domain/user/rest/UserRestController.java b/src/main/java/de/filefighter/rest/domain/user/rest/UserRestController.java index 4b4b1b38..1dc9a1a1 100644 --- a/src/main/java/de/filefighter/rest/domain/user/rest/UserRestController.java +++ b/src/main/java/de/filefighter/rest/domain/user/rest/UserRestController.java @@ -49,7 +49,7 @@ public ResponseEntity getAccessTokenAndUserInfoByRefreshTokenAndUse @PathVariable long userId, @RequestHeader(value = "Authorization", defaultValue = AUTHORIZATION_BEARER_PREFIX + "token") String refreshToken) { - LOG.info("Requested refreshing for user {} with token {}.", userId, refreshToken); + LOG.info("Requested login for user {} with token {}.", userId, refreshToken); return userRestService.getAccessTokenByRefreshTokenAndUserId(refreshToken, userId); } diff --git a/src/main/java/de/filefighter/rest/domain/user/rest/UserRestService.java b/src/main/java/de/filefighter/rest/domain/user/rest/UserRestService.java index 8771d8e0..d5e2048c 100644 --- a/src/main/java/de/filefighter/rest/domain/user/rest/UserRestService.java +++ b/src/main/java/de/filefighter/rest/domain/user/rest/UserRestService.java @@ -1,9 +1,12 @@ package de.filefighter.rest.domain.user.rest; +import de.filefighter.rest.domain.token.business.AccessTokenBusinessService; import de.filefighter.rest.domain.token.data.dto.AccessToken; import de.filefighter.rest.domain.token.data.dto.RefreshToken; +import de.filefighter.rest.domain.user.business.UserBusinessService; import de.filefighter.rest.domain.user.data.dto.User; import de.filefighter.rest.domain.user.data.dto.UserRegisterForm; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -11,19 +14,35 @@ @Service public class UserRestService implements UserRestServiceInterface { + private final UserBusinessService userBusinessService; + private final AccessTokenBusinessService accessTokenBusinessService; + + public UserRestService(UserBusinessService userBusinessService, AccessTokenBusinessService accessTokenBusinessService) { + this.userBusinessService = userBusinessService; + this.accessTokenBusinessService = accessTokenBusinessService; + } + @Override - public ResponseEntity getUserByAccessTokenAndUserId(String accessToken, long userId) { - return null; + public ResponseEntity getUserByAccessTokenAndUserId(String accessTokenValue, long userId) { + String cleanValue = accessTokenBusinessService.checkBearerHeader(accessTokenValue); + AccessToken accessToken = accessTokenBusinessService.findAccessTokenByValueAndUserId(cleanValue, userId); + User user = userBusinessService.getUserByAccessTokenAndUserId(accessToken, userId); + return new ResponseEntity<>(user, HttpStatus.OK); } @Override public ResponseEntity getRefreshTokenWithUsernameAndPassword(String base64encodedUserAndPassword) { - return null; + User user = userBusinessService.getUserByUsernameAndPassword(base64encodedUserAndPassword); + RefreshToken refreshToken = userBusinessService.getRefreshTokenForUser(user); + return new ResponseEntity<>(refreshToken, HttpStatus.OK); } @Override public ResponseEntity getAccessTokenByRefreshTokenAndUserId(String refreshToken, long userId) { - return null; + String cleanValue = accessTokenBusinessService.checkBearerHeader(refreshToken); + User user = userBusinessService.getUserByRefreshTokenAndUserId(cleanValue, userId); + AccessToken accessToken = accessTokenBusinessService.getValidAccessTokenForUser(user); + return new ResponseEntity<>(accessToken, HttpStatus.OK); } @Override diff --git a/src/main/java/de/filefighter/rest/domain/user/role/GroupRepository.java b/src/main/java/de/filefighter/rest/domain/user/role/GroupRepository.java index 61d87549..557a2e10 100644 --- a/src/main/java/de/filefighter/rest/domain/user/role/GroupRepository.java +++ b/src/main/java/de/filefighter/rest/domain/user/role/GroupRepository.java @@ -9,7 +9,7 @@ public class GroupRepository { //TODO: test this. public Groups getRoleById(long id) { for (Groups role : roles) { - if (role.getRoleId() == id) { + if (role.getGroupId() == id) { return role; } } @@ -17,7 +17,11 @@ public Groups getRoleById(long id) { } public Groups[] getRolesByIds(long... ids){ - Groups[] roles = new Groups[ids.length]; //TODO: check this again. + Groups[] roles; + if(null == ids){ + return new Groups[0]; + } + roles = new Groups[ids.length]; for (int i = 0; i < ids.length; i++) { roles[i] = this.getRoleById(ids[i]); diff --git a/src/main/java/de/filefighter/rest/domain/user/role/Groups.java b/src/main/java/de/filefighter/rest/domain/user/role/Groups.java index a6ca7f18..616a6dff 100644 --- a/src/main/java/de/filefighter/rest/domain/user/role/Groups.java +++ b/src/main/java/de/filefighter/rest/domain/user/role/Groups.java @@ -5,16 +5,16 @@ public enum Groups { FAMILY(0, "Family"), ADMIN(1, "Admin"); - private final long roleId; + private final long groupId; private final String displayName; Groups(long roleId, String displayName) { - this.roleId = roleId; + this.groupId = roleId; this.displayName = displayName; } - public long getRoleId() { - return roleId; + public long getGroupId() { + return groupId; } public String getDisplayName() { diff --git a/src/test/java/de/filefighter/rest/cucumber/CommonCucumberSteps.java b/src/test/java/de/filefighter/rest/cucumber/CommonCucumberSteps.java index 68b3d581..689e799b 100644 --- a/src/test/java/de/filefighter/rest/cucumber/CommonCucumberSteps.java +++ b/src/test/java/de/filefighter/rest/cucumber/CommonCucumberSteps.java @@ -6,7 +6,6 @@ import de.filefighter.rest.RestApplicationIntegrationTest; import de.filefighter.rest.domain.filesystem.data.persistance.FileSystemEntity; import de.filefighter.rest.domain.filesystem.data.persistance.FileSystemRepository; -import de.filefighter.rest.domain.token.data.persistance.AccessTokenEntity; import de.filefighter.rest.domain.token.data.persistance.AccessTokenRepository; import de.filefighter.rest.domain.user.data.persistance.UserEntity; import de.filefighter.rest.domain.user.data.persistance.UserRepository; @@ -16,11 +15,8 @@ import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; -import java.time.Instant; import java.util.Arrays; -import java.util.UUID; -import static de.filefighter.rest.domain.token.business.AccessTokenBusinessService.ACCESS_TOKEN_DURATION_IN_SECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -54,16 +50,6 @@ public void userExists(long userId) { .build()); } - @And("user {long} has access token {string}") - public void userHasAccessToken(long userId, String accessTokenValue) { - accessTokenRepository.save(AccessTokenEntity - .builder() - .userId(userId) - .value(accessTokenValue) - .validUntil(Instant.now().getEpochSecond() + ACCESS_TOKEN_DURATION_IN_SECONDS) - .build()); - } - @And("user with id {long} exists and has username {string}, password {string} and refreshToken {string}") public void userWithIdExistsAndHasUsernamePasswordAndRefreshToken(long userId, String username, String password, String refreshTokenValue) { userRepository.save(UserEntity @@ -83,15 +69,15 @@ public void fileOrFolderExistsWithIdAndPath(String fileOrFolder, long fsItemId, System.out.println(Arrays.toString(names)); - // create root dir. + // build root dir. fileSystemRepository.save(FileSystemEntity .builder() .isFile(false) .path(completeFilePath.toString()) - .create()); + .build()); - // create all files and folders. + // build all files and folders. for (int i = 0; i < names.length; i++) { if (!names[i].isEmpty() && !names[i].isBlank()) { boolean isLastOne = i == names.length - 1; @@ -102,7 +88,7 @@ public void fileOrFolderExistsWithIdAndPath(String fileOrFolder, long fsItemId, .builder() .isFile(false) .path(completeFilePath.toString()) - .create()); + .build()); System.out.println("folder: "+completeFilePath.toString()); }else{ System.out.println("last one: "+names[i]); @@ -111,7 +97,7 @@ public void fileOrFolderExistsWithIdAndPath(String fileOrFolder, long fsItemId, .builder() .isFile(true) .id(fsItemId) - .create()); + .build()); } else if (fileOrFolder.equals("folder")) { completeFilePath.append(names[i]).append("/"); fileSystemRepository.save(FileSystemEntity @@ -119,7 +105,7 @@ public void fileOrFolderExistsWithIdAndPath(String fileOrFolder, long fsItemId, .isFile(false) .id(fsItemId) .path(completeFilePath.toString()) - .create()); + .build()); } else { throw new IllegalArgumentException("Found not valid string for FileOrFolder in Steps file."); } @@ -148,7 +134,7 @@ public void responseContainsKeyAndValue(String key, String value) throws JsonPro @And("response contains the user with id {long}") public void responseContainsTheUserWithId(long userId) throws JsonProcessingException { JsonNode rootNode = objectMapper.readTree(latestResponse.getBody()); - long actualValue = rootNode.get("userId").asLong(); + long actualValue = rootNode.get("id").asLong(); assertEquals(userId, actualValue); } diff --git a/src/test/java/de/filefighter/rest/cucumber/SystemHealthSteps.java b/src/test/java/de/filefighter/rest/cucumber/SystemHealthSteps.java index 813926d6..57498ad2 100644 --- a/src/test/java/de/filefighter/rest/cucumber/SystemHealthSteps.java +++ b/src/test/java/de/filefighter/rest/cucumber/SystemHealthSteps.java @@ -1,12 +1,21 @@ package de.filefighter.rest.cucumber; import de.filefighter.rest.RestApplicationIntegrationTest; +import io.cucumber.java.en.And; import io.cucumber.java.en.When; import org.springframework.http.HttpMethod; +import java.util.Timer; +import java.util.concurrent.TimeUnit; + public class SystemHealthSteps extends RestApplicationIntegrationTest { @When("the systemHealth endpoint is requested") public void theSystemHealthEndpointIsRequested() { executeRestApiCall(HttpMethod.GET, "health/"); } + + @And("the user waits for {int} second\\(s)") + public void theUserWaitsForSecondS(int seconds) throws InterruptedException { + TimeUnit.SECONDS.sleep(seconds); + } } diff --git a/src/test/java/de/filefighter/rest/cucumber/UserAuthorizationSteps.java b/src/test/java/de/filefighter/rest/cucumber/UserAuthorizationSteps.java index fce5a740..872b63a3 100644 --- a/src/test/java/de/filefighter/rest/cucumber/UserAuthorizationSteps.java +++ b/src/test/java/de/filefighter/rest/cucumber/UserAuthorizationSteps.java @@ -1,27 +1,127 @@ package de.filefighter.rest.cucumber; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import de.filefighter.rest.RestApplicationIntegrationTest; +import de.filefighter.rest.domain.token.data.persistance.AccessTokenEntity; +import de.filefighter.rest.domain.token.data.persistance.AccessTokenRepository; import io.cucumber.java.en.And; -import io.cucumber.java.en.Then; +import io.cucumber.java.en.Given; import io.cucumber.java.en.When; +import org.bson.internal.Base64; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpMethod; -import static org.junit.jupiter.api.Assertions.assertTrue; +import java.time.Instant; +import java.util.HashMap; +import java.util.UUID; + +import static com.mongodb.internal.connection.tlschannel.util.Util.assertTrue; +import static de.filefighter.rest.configuration.RestConfiguration.*; +import static de.filefighter.rest.domain.token.business.AccessTokenBusinessService.ACCESS_TOKEN_DURATION_IN_SECONDS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; public class UserAuthorizationSteps extends RestApplicationIntegrationTest { + private final ObjectMapper objectMapper; + private final AccessTokenRepository accessTokenRepository; + + @Autowired + public UserAuthorizationSteps(AccessTokenRepository accessTokenRepository) { + this.objectMapper = new ObjectMapper(); + this.accessTokenRepository = accessTokenRepository; + } + + + @Given("accessToken with value {string} exists for user {long} and is valid until {long}") + public void accessTokenWithValueExistsForUserAndIsValidUntil(String tokenValue, long userId, long validUntil) { + accessTokenRepository.save(AccessTokenEntity.builder() + .userId(userId) + .value(tokenValue) + .validUntil(validUntil) + .build()); + } + + @Given("accessToken with value {string} exists for user {long}") + public void accessTokenWithValueExistsForUser(String tokenValue, long userId) { + accessTokenRepository.save(AccessTokenEntity.builder() + .userId(userId) + .value(tokenValue) + .validUntil(Instant.now().getEpochSecond() + ACCESS_TOKEN_DURATION_IN_SECONDS).build()); + } + @When("user requests login with username {string} and password {string}") public void userRequestsLoginWithUsernameAndPassword(String username, String password) { + String authString = username + ":" + password; + String base64encoded = Base64.encode(authString.getBytes()); + base64encoded = AUTHORIZATION_BASIC_PREFIX + base64encoded; + + HashMap authHeader = new HashMap<>(); + authHeader.put("Authorization", base64encoded); + + String url = BASE_API_URI + USER_BASE_URI + "login"; + + executeRestApiCall(HttpMethod.GET, url, authHeader); } @When("user requests accessToken with refreshToken {string} and userId {long}") public void userRequestsAccessTokenWithRefreshTokenAndUserId(String refreshTokenValue, long userId) { - } + String authHeaderString = AUTHORIZATION_BEARER_PREFIX + refreshTokenValue; + String url = BASE_API_URI + USER_BASE_URI + userId + "/login"; - @And("response contains valid accessToken") - public void responseContainsValidAccessToken() { + HashMap authHeader = new HashMap<>(); + authHeader.put("Authorization", authHeaderString); + + executeRestApiCall(HttpMethod.GET, url, authHeader); } @When("user requests userInfo with accessToken {string} and userId {long}") public void userRequestsUserInfoWithAccessTokenAndUserId(String accessTokenValue, long userId) { + String authHeaderString = AUTHORIZATION_BEARER_PREFIX + accessTokenValue; + String url = BASE_API_URI + USER_BASE_URI + userId + "/info"; + + HashMap authHeader = new HashMap<>(); + authHeader.put("Authorization", authHeaderString); + + executeRestApiCall(HttpMethod.GET, url, authHeader); + } + + @And("response contains valid accessToken for user {long}") + public void responseContainsValidAccessTokenForUser(long userId) throws JsonProcessingException { + JsonNode rootNode = objectMapper.readTree(latestResponse.getBody()); + String tokenValue = rootNode.get("token").asText(); + long actualUserId = rootNode.get("userId").asLong(); + long validUntil = rootNode.get("validUntil").asLong(); + + int expectedTokenLength = UUID.randomUUID().toString().length(); + int actualTokenLength = tokenValue.length(); + boolean isStillViable = validUntil > Instant.now().getEpochSecond(); + + assertEquals(expectedTokenLength, actualTokenLength); + assertTrue(isStillViable); + assertEquals(userId, actualUserId); + } + + @And("response contains refreshToken {string} and the user with id {long}") + public void responseContainsRefreshTokenAndTheUserWithId(String refreshToken, long userId) throws JsonProcessingException { + JsonNode rootNode = objectMapper.readTree(latestResponse.getBody()); + String actualRefreshToken = rootNode.get("refreshToken").asText(); + JsonNode userNode = rootNode.get("user"); + long actualUserId = userNode.get("id").asLong(); + + assertEquals(userId, actualUserId); + assertEquals(refreshToken, actualRefreshToken); + } + + @And("response contains valid accessToken for user {long} with a different value than {string}") + public void responseContainsValidAccessTokenForUserWithADifferentValueThan(long userId, String differentTokenValue) throws JsonProcessingException { + JsonNode rootNode = objectMapper.readTree(latestResponse.getBody()); + String actualTokenValue = rootNode.get("token").asText(); + long actualUserId = rootNode.get("userId").asLong(); + + assertEquals(userId, actualUserId); + assertNotEquals(differentTokenValue, actualTokenValue); } } diff --git a/src/test/java/de/filefighter/rest/domain/common/UtilsUnitTest.java b/src/test/java/de/filefighter/rest/domain/common/UtilsUnitTest.java new file mode 100644 index 00000000..0f192d85 --- /dev/null +++ b/src/test/java/de/filefighter/rest/domain/common/UtilsUnitTest.java @@ -0,0 +1,37 @@ +package de.filefighter.rest.domain.common; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@SuppressWarnings(value = "ConstantConditions") +class UtilsUnitTest { + + @Test + void stringIsValid() { + String string = "string"; + boolean actual = Utils.stringIsValid(string); + assertTrue(actual); + } + + @Test + void stringIsNull() { + String string = null; + boolean actual = Utils.stringIsValid(string); + assertFalse(actual); + } + + @Test + void stringIsEmpty() { + String string = ""; + boolean actual = Utils.stringIsValid(string); + assertFalse(actual); + } + + @Test + void stringIsBlank() { + String string = ""; + boolean actual = Utils.stringIsValid(string); + assertFalse(actual); + } +} \ No newline at end of file diff --git a/src/test/java/de/filefighter/rest/domain/filesystem/rest/FileSystemRestControllerUnitTest.java b/src/test/java/de/filefighter/rest/domain/filesystem/rest/FileSystemRestControllerUnitTest.java index 954b9a27..28452505 100644 --- a/src/test/java/de/filefighter/rest/domain/filesystem/rest/FileSystemRestControllerUnitTest.java +++ b/src/test/java/de/filefighter/rest/domain/filesystem/rest/FileSystemRestControllerUnitTest.java @@ -28,7 +28,7 @@ void getContentsOfFolder() { File dummyFile = new File(); ResponseEntity expectedModel = new ResponseEntity<>(FolderContents.builder() .files(new File[]{dummyFile}) - .folders(new Folder[]{dummyFolder}).create(), OK); + .folders(new Folder[]{dummyFolder}).build(), OK); String path= "/root/data.txt"; String token = "token"; @@ -72,7 +72,7 @@ void uploadFileOrFolder() { File file = new File(); ResponseEntity expectedModel = new ResponseEntity<>(file, OK); - FileSystemItemUpdate fileSystemItemUpdate = FileSystemItemUpdate.create().name("ugabuga").build(); + FileSystemItemUpdate fileSystemItemUpdate = FileSystemItemUpdate.builder().name("ugabuga").build(); String token = "token"; when(fileSystemRestServiceMock.uploadFileSystemItemWithAccessToken(fileSystemItemUpdate, token)).thenReturn(expectedModel); @@ -87,7 +87,7 @@ void updateExistingFileOrFolder() { ResponseEntity expectedModel = new ResponseEntity<>(file, OK); long id = 420L; - FileSystemItemUpdate fileSystemItemUpdate = FileSystemItemUpdate.create().name("ugabuga").build(); + FileSystemItemUpdate fileSystemItemUpdate = FileSystemItemUpdate.builder().name("ugabuga").build(); String token = "token"; when(fileSystemRestServiceMock.updatedFileSystemItemWithIdAndAccessToken(id, fileSystemItemUpdate, token)).thenReturn(expectedModel); diff --git a/src/test/java/de/filefighter/rest/domain/health/rest/SystemHealthRestControllerUnitTest.java b/src/test/java/de/filefighter/rest/domain/health/rest/SystemHealthRestControllerUnitTest.java deleted file mode 100644 index 187a9086..00000000 --- a/src/test/java/de/filefighter/rest/domain/health/rest/SystemHealthRestControllerUnitTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package de.filefighter.rest.domain.health.rest; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; - -import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -class SystemHealthRestControllerUnitTest { - - private final SystemHealthRestService systemHealthRestServiceMock = mock(SystemHealthRestService.class); - private MockMvc mockMvc; - - @BeforeEach - public void setUp() { - SystemHealthRestController systemHealthRestController = new SystemHealthRestController(systemHealthRestServiceMock); - mockMvc = MockMvcBuilders.standaloneSetup(systemHealthRestController).build(); - } - - @Test - void getSystemHealthInfo() throws Exception { - mockMvc.perform(get("/health")) - .andExpect(status().isOk()) - .andReturn(); - - verify(systemHealthRestServiceMock, times(1)).getSystemHealth(); - } -} \ No newline at end of file diff --git a/src/test/java/de/filefighter/rest/domain/health/rest/SystemHealthRestIntegrationTest.java b/src/test/java/de/filefighter/rest/domain/health/rest/SystemHealthRestIntegrationTest.java deleted file mode 100644 index fd13c08f..00000000 --- a/src/test/java/de/filefighter/rest/domain/health/rest/SystemHealthRestIntegrationTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package de.filefighter.rest.domain.health.rest; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import de.filefighter.rest.domain.filesystem.data.persistance.FileSystemRepository; -import de.filefighter.rest.domain.token.data.persistance.AccessTokenRepository; -import de.filefighter.rest.domain.user.data.persistance.UserEntity; -import de.filefighter.rest.domain.user.data.persistance.UserRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -public class SystemHealthRestIntegrationTest { - - @LocalServerPort - private int port; - - private final Logger LOG = LoggerFactory.getLogger(SystemHealthRestIntegrationTest.class); - private final ObjectMapper objectMapper; - private final TestRestTemplate restTemplate; - private final UserRepository userRepository; - private final FileSystemRepository fileSystemRepository; - private final AccessTokenRepository accessTokenRepository; - - @Autowired - public SystemHealthRestIntegrationTest(TestRestTemplate restTemplate, UserRepository userRepository, FileSystemRepository fileSystemRepository, AccessTokenRepository accessTokenRepository) { - this.objectMapper = new ObjectMapper(); - this.restTemplate = restTemplate; - this.userRepository = userRepository; - this.fileSystemRepository = fileSystemRepository; - this.accessTokenRepository = accessTokenRepository; - } - - @BeforeEach - public void cleanDbs() { - LOG.info("Cleaning Databases."); - userRepository.deleteAll(); - fileSystemRepository.deleteAll(); - accessTokenRepository.deleteAll(); - } - - @Test - public void healthCheckShouldContainVariablesAndCorrectValues() throws JsonProcessingException { - LOG.info("Preloading default admin user: " + userRepository.save(UserEntity - .builder() - .userId(0L) - .username("admin") - .password("admin") - .refreshToken("refreshToken1234") - .roleIds(new long[]{0, 1}) - .build())); - String jsonString = this.restTemplate.getForObject("http://localhost:" + port + "/health", String.class); - - // Note when a key does not exist, a NullPointerException will be thrown. - JsonNode root = objectMapper.readTree(jsonString); - String uptime = root.get("uptimeInSeconds").asText(); - String userCount = root.get("userCount").asText(); - - assertTrue(Integer.parseInt(uptime) > 0); - assertEquals(1, Integer.parseInt(userCount)); - } -} \ No newline at end of file diff --git a/src/test/java/de/filefighter/rest/domain/health/rest/SystemHealthRestServiceUnitTest.java b/src/test/java/de/filefighter/rest/domain/health/rest/SystemHealthRestServiceUnitTest.java index ca18b206..8b8cc210 100644 --- a/src/test/java/de/filefighter/rest/domain/health/rest/SystemHealthRestServiceUnitTest.java +++ b/src/test/java/de/filefighter/rest/domain/health/rest/SystemHealthRestServiceUnitTest.java @@ -23,7 +23,7 @@ public void setUp() { @Test void getSystemHealth() { - SystemHealth dummyHealth = SystemHealth.builder().uptimeInSeconds(420).create(); + SystemHealth dummyHealth = SystemHealth.builder().uptimeInSeconds(420).build(); ResponseEntity expectedModel = new ResponseEntity<>(dummyHealth, HttpStatus.OK); when(systemHealthBusinessServiceMock.getCurrentSystemHealthInfo()).thenReturn(dummyHealth); diff --git a/src/test/java/de/filefighter/rest/domain/permission/rest/PermissionRestControllerUnitTest.java b/src/test/java/de/filefighter/rest/domain/permission/rest/PermissionRestControllerUnitTest.java index d35d0fea..54474ebf 100644 --- a/src/test/java/de/filefighter/rest/domain/permission/rest/PermissionRestControllerUnitTest.java +++ b/src/test/java/de/filefighter/rest/domain/permission/rest/PermissionRestControllerUnitTest.java @@ -30,7 +30,7 @@ void setUp() { void getPermissionSetForFileOrFolder() { String token = "token"; long id = 420; - User dummyUser = User.builder().create(); + User dummyUser = User.builder().build(); ResponseEntity expected = new ResponseEntity<>(new PermissionSet(null, null, new User[]{dummyUser}, null), OK); when(permissionRestService.getPermissionSetByIdAndToken(id, token)).thenReturn(expected); diff --git a/src/test/java/de/filefighter/rest/domain/token/business/AccessTokenBusinessServiceUnitTest.java b/src/test/java/de/filefighter/rest/domain/token/business/AccessTokenBusinessServiceUnitTest.java new file mode 100644 index 00000000..997b9b0b --- /dev/null +++ b/src/test/java/de/filefighter/rest/domain/token/business/AccessTokenBusinessServiceUnitTest.java @@ -0,0 +1,151 @@ +package de.filefighter.rest.domain.token.business; + +import de.filefighter.rest.domain.token.data.dto.AccessToken; +import de.filefighter.rest.domain.token.data.persistance.AccessTokenEntity; +import de.filefighter.rest.domain.token.data.persistance.AccessTokenRepository; +import de.filefighter.rest.domain.user.data.dto.User; +import de.filefighter.rest.domain.user.exceptions.UserNotAuthenticatedException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class AccessTokenBusinessServiceUnitTest { + + private final AccessTokenRepository accessTokenRepositoryMock = mock(AccessTokenRepository.class); + private final AccessTokenDtoService accessTokenDtoService = mock(AccessTokenDtoService.class); + private AccessTokenBusinessService accessTokenBusinessService; + + @BeforeEach + void setUp() { + accessTokenBusinessService = new AccessTokenBusinessService(accessTokenRepositoryMock, accessTokenDtoService); + } + + @Test + void getValidAccessTokenForUserWhenNoTokenExists() { + long dummyId = 1234; + User dummyUser = User.builder().id(dummyId).build(); + AccessToken dummyAccessToken = AccessToken.builder().userId(dummyId).build(); + AccessTokenEntity dummyAccessTokenEntity = AccessTokenEntity.builder().userId(dummyId).build(); + + when(accessTokenRepositoryMock.findByUserId(dummyId)).thenReturn(null); + when(accessTokenRepositoryMock.save(any())).thenReturn(dummyAccessTokenEntity); + when(accessTokenDtoService.createDto(dummyAccessTokenEntity)).thenReturn(dummyAccessToken); + + AccessToken accessToken = accessTokenBusinessService.getValidAccessTokenForUser(dummyUser); + assertEquals(dummyAccessToken, accessToken); + } + + @Test + void getValidAccessTokenForUserWhenTokenExists() { + long dummyId = 1234; + User dummyUser = User.builder().id(dummyId).build(); + AccessToken dummyAccessToken = AccessToken.builder().userId(dummyId).build(); + AccessTokenEntity dummyAccessTokenEntity = AccessTokenEntity + .builder() + .userId(dummyId) + .validUntil(Instant.now().getEpochSecond() + AccessTokenBusinessService.ACCESS_TOKEN_DURATION_IN_SECONDS + 100) + .build(); + + when(accessTokenRepositoryMock.findByUserId(dummyId)).thenReturn(dummyAccessTokenEntity); + when(accessTokenDtoService.createDto(dummyAccessTokenEntity)).thenReturn(dummyAccessToken); + + AccessToken accessToken = accessTokenBusinessService.getValidAccessTokenForUser(dummyUser); + assertEquals(dummyAccessToken, accessToken); + } + + @Test + void getValidAccessTokenForUserWhenTokenExistsButIsInvalid() { + long dummyId = 1234; + User dummyUser = User.builder().id(dummyId).build(); + AccessToken dummyAccessToken = AccessToken.builder().userId(dummyId).build(); + AccessTokenEntity dummyAccessTokenEntity = AccessTokenEntity + .builder() + .userId(dummyId) + .validUntil(Instant.now().getEpochSecond()) + .build(); + + when(accessTokenRepositoryMock.findByUserId(dummyId)).thenReturn(dummyAccessTokenEntity); + when(accessTokenRepositoryMock.save(any())).thenReturn(dummyAccessTokenEntity); + when(accessTokenDtoService.createDto(dummyAccessTokenEntity)).thenReturn(dummyAccessToken); + + AccessToken accessToken = accessTokenBusinessService.getValidAccessTokenForUser(dummyUser); + + assertEquals(dummyAccessToken, accessToken); + verify(accessTokenRepositoryMock, times(0)).save(dummyAccessTokenEntity); // the newly saved token is different. + } + + @Test + void findAccessTokenByValueAndUserIdWithInvalidToken() { + String tokenValue = ""; + long userId = 1234; + + assertThrows(IllegalArgumentException.class, () -> + accessTokenBusinessService.findAccessTokenByValueAndUserId(tokenValue, userId) + ); + } + + @Test + void findAccessTokenByValueAndUserIdWithTokenNotFound() { + String tokenValue = "value"; + long userId = 1234; + + when(accessTokenRepositoryMock.findByUserIdAndValue(userId, tokenValue)).thenReturn(null); + + assertThrows(UserNotAuthenticatedException.class, () -> + accessTokenBusinessService.findAccessTokenByValueAndUserId(tokenValue, userId) + ); + } + + @Test + void findAccessTokenByValueAndUserIdWithFoundToken() { + String tokenValue = "validToken"; + long userId = 1234; + AccessToken dummyAccessToken = AccessToken.builder().build(); + AccessTokenEntity dummyAccessTokenEntity = AccessTokenEntity.builder().build(); + + when(accessTokenRepositoryMock.findByUserIdAndValue(userId, tokenValue)).thenReturn(dummyAccessTokenEntity); + when(accessTokenDtoService.createDto(dummyAccessTokenEntity)).thenReturn(dummyAccessToken); + + AccessToken actual = accessTokenBusinessService.findAccessTokenByValueAndUserId(tokenValue, userId); + + assertEquals(dummyAccessToken, actual); + } + + @Test + void generateRandomTokenValue() { + String generatedToken = accessTokenBusinessService.generateRandomTokenValue(); + assertEquals(36, generatedToken.length()); + } + + @Test + void checkBearerHeaderWithWrongHeader(){ + String header0 = "wrongHeader"; + String header1 = ""; + String header2 = "Bearer: "; + + assertThrows(UserNotAuthenticatedException.class, () -> + accessTokenBusinessService.checkBearerHeader(header0) + ); + assertThrows(UserNotAuthenticatedException.class, () -> + accessTokenBusinessService.checkBearerHeader(header1) + ); + assertThrows(UserNotAuthenticatedException.class, () -> + accessTokenBusinessService.checkBearerHeader(header2) + ); + } + + @Test + void checkBearerHeaderWithCorrectHeader(){ + String header = "Bearer: something"; + String expected = "something"; + + String actual = accessTokenBusinessService.checkBearerHeader(header); + + assertEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/de/filefighter/rest/domain/token/business/AccessTokenDtoServiceUnitTest.java b/src/test/java/de/filefighter/rest/domain/token/business/AccessTokenDtoServiceUnitTest.java new file mode 100644 index 00000000..705d0e76 --- /dev/null +++ b/src/test/java/de/filefighter/rest/domain/token/business/AccessTokenDtoServiceUnitTest.java @@ -0,0 +1,65 @@ +package de.filefighter.rest.domain.token.business; + +import de.filefighter.rest.domain.token.data.dto.AccessToken; +import de.filefighter.rest.domain.token.data.persistance.AccessTokenEntity; +import de.filefighter.rest.domain.token.data.persistance.AccessTokenRepository; +import de.filefighter.rest.domain.token.exceptions.AccessTokenNotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class AccessTokenDtoServiceUnitTest { + + private final AccessTokenRepository accessTokenRepository = mock(AccessTokenRepository.class); + private AccessTokenDtoService accessTokenDtoService; + + @BeforeEach + void setUp() { + accessTokenDtoService = new AccessTokenDtoService(accessTokenRepository); + } + + @Test + void createDto() { + AccessTokenEntity dummyEntity = AccessTokenEntity.builder() + .validUntil(420) + .userId(240) + .value("token") + .build(); + + AccessToken actual = accessTokenDtoService.createDto(dummyEntity); + + assertEquals(dummyEntity.getUserId(), actual.getUserId()); + assertEquals(dummyEntity.getValidUntil(), actual.getValidUntil()); + assertEquals(dummyEntity.getValue(), actual.getToken()); + } + + @Test + void findEntityNotSuccessfully() { + long userId = 420; + String token = "token"; + AccessToken dummyToken = AccessToken.builder().userId(userId).token(token).build(); + + when(accessTokenRepository.findByUserIdAndValue(userId, token)).thenReturn(null); + + assertThrows(AccessTokenNotFoundException.class, () -> + accessTokenDtoService.findEntity(dummyToken) + ); + } + + @Test + void findEntitySuccessfully() { + long userId = 420; + String token = "token"; + AccessToken dummyToken = AccessToken.builder().userId(userId).token(token).build(); + AccessTokenEntity expected = AccessTokenEntity.builder().userId(userId).value(token).build(); + + when(accessTokenRepository.findByUserIdAndValue(userId, token)).thenReturn(expected); + + AccessTokenEntity actual = accessTokenDtoService.findEntity(dummyToken); + + assertEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/de/filefighter/rest/domain/user/business/UserBusinessServiceUnitTest.java b/src/test/java/de/filefighter/rest/domain/user/business/UserBusinessServiceUnitTest.java index 919ba089..857a99b3 100644 --- a/src/test/java/de/filefighter/rest/domain/user/business/UserBusinessServiceUnitTest.java +++ b/src/test/java/de/filefighter/rest/domain/user/business/UserBusinessServiceUnitTest.java @@ -1,31 +1,200 @@ package de.filefighter.rest.domain.user.business; +import de.filefighter.rest.domain.token.data.dto.AccessToken; +import de.filefighter.rest.domain.token.data.dto.RefreshToken; +import de.filefighter.rest.domain.user.data.dto.User; +import de.filefighter.rest.domain.user.data.persistance.UserEntity; import de.filefighter.rest.domain.user.data.persistance.UserRepository; +import de.filefighter.rest.domain.user.exceptions.UserNotAuthenticatedException; +import de.filefighter.rest.domain.user.exceptions.UserNotFoundException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static de.filefighter.rest.configuration.RestConfiguration.AUTHORIZATION_BASIC_PREFIX; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; class UserBusinessServiceUnitTest { private final UserRepository userRepositoryMock = mock(UserRepository.class); + private final UserDtoService userDtoServiceMock = mock(UserDtoService.class); private UserBusinessService userBusinessService; @BeforeEach void setUp() { - userBusinessService = new UserBusinessService(userRepositoryMock); + userBusinessService = new UserBusinessService(userRepositoryMock, userDtoServiceMock); } @Test void getUserCount() { - long expectedCount = 420; + long count = 20; - when(userRepositoryMock.count()).thenReturn(expectedCount); + when(userRepositoryMock.count()).thenReturn(count); - long actualCount = userBusinessService.getUserCount(); + long actual = userBusinessService.getUserCount(); - assertEquals(expectedCount, actualCount); + assertEquals(count, actual); + } + + @Test + void getUserByUsernameAndPasswordThrowsErrors() { + String notValid = ""; + String validButDoesntMatch = "something"; + String matchesButIsNotSupportedEncoding = AUTHORIZATION_BASIC_PREFIX + "���"; + String withoutFormalRequirements = AUTHORIZATION_BASIC_PREFIX + "dWdhYnVnYXBhc3N3b3Jk"; //ugabugapassword + String userNotFound = AUTHORIZATION_BASIC_PREFIX + "dXNlcjpwYXNzd29yZA=="; // user:password + + assertThrows(UserNotAuthenticatedException.class, () -> + userBusinessService.getUserByUsernameAndPassword(notValid) + ); + assertThrows(UserNotAuthenticatedException.class, () -> + userBusinessService.getUserByUsernameAndPassword(validButDoesntMatch) + ); + assertThrows(RuntimeException.class, () -> + userBusinessService.getUserByUsernameAndPassword(matchesButIsNotSupportedEncoding) + ); + assertThrows(UserNotAuthenticatedException.class, () -> + userBusinessService.getUserByUsernameAndPassword(withoutFormalRequirements) + ); + + when(userRepositoryMock.findByUsernameAndPassword("user", "password")).thenReturn(null); + assertThrows(UserNotFoundException.class, () -> + userBusinessService.getUserByUsernameAndPassword(userNotFound) + ); + } + + @Test + void getUserByUsernameAndPasswordWorksCorrectly() { + String header = AUTHORIZATION_BASIC_PREFIX + "dXNlcjpwYXNzd29yZA=="; // user:password + User dummyUser = User.builder().build(); + UserEntity dummyEntity = UserEntity.builder().build(); + + when(userRepositoryMock.findByUsernameAndPassword("user", "password")).thenReturn(dummyEntity); + when(userDtoServiceMock.createDto(dummyEntity)).thenReturn(dummyUser); + + User actual = userBusinessService.getUserByUsernameAndPassword(header); + assertEquals(dummyUser, actual); + } + + @Test + void getRefreshTokenForUserWithoutUser() { + String invalidString = ""; + long userId = 420; + String username = "someString"; + + User dummyUser = User.builder().id(userId).username(username).build(); + + when(userRepositoryMock.findByUserIdAndUsername(userId, username)).thenReturn(null); + + assertThrows(UserNotFoundException.class, () -> + userBusinessService.getRefreshTokenForUser(dummyUser) + ); + } + + @Test + void getRefreshTokenForUserWithInvalidString() { + String invalidString = ""; + long userId = 420; + String username = "someString"; + + User dummyUser = User.builder().id(userId).username(username).build(); + UserEntity dummyEntity = UserEntity.builder().refreshToken(invalidString).build(); + + when(userRepositoryMock.findByUserIdAndUsername(userId, username)).thenReturn(dummyEntity); + + assertThrows(IllegalStateException.class, () -> + userBusinessService.getRefreshTokenForUser(dummyUser) + ); + } + + @Test + void getCorrectRefreshTokenForUser() { + long userId = 420; + String username = "someString"; + String refreshToken = "someToken"; + User dummyUser = User.builder().id(userId).username(username).build(); + UserEntity dummyEntity = UserEntity.builder().refreshToken(refreshToken).build(); + RefreshToken expected = RefreshToken.builder().refreshToken(refreshToken).user(dummyUser).build(); + + when(userRepositoryMock.findByUserIdAndUsername(userId, username)).thenReturn(dummyEntity); + + RefreshToken actual = userBusinessService.getRefreshTokenForUser(dummyUser); + assertEquals(expected, actual); + } + + // -------------------------------------------------------------------------------------------- // + + @Test + void getUserByAccessTokenAndUserIdWithInvalidUserId() { + long userId = 420; + AccessToken dummyAccessToken = AccessToken.builder().userId(300).build(); + + assertThrows(UserNotAuthenticatedException.class, () -> + userBusinessService.getUserByAccessTokenAndUserId(dummyAccessToken, userId) + ); + } + + @Test + void getUserByAccessTokenAndUserIdWithoutUser() { + long userId = 420; + AccessToken accessToken = AccessToken.builder().userId(userId).build(); + + when(userRepositoryMock.findByUserId(userId)).thenReturn(null); + + assertThrows(UserNotFoundException.class, () -> + userBusinessService.getUserByAccessTokenAndUserId(accessToken, userId) + ); + } + + @Test + void getUserByAccessTokenAndUserIdCorrectly() { + long userId = 420; + AccessToken accessToken = AccessToken.builder().userId(userId).build(); + User dummyUser = User.builder().id(userId).build(); + UserEntity dummyEntity = UserEntity.builder().build(); + + when(userRepositoryMock.findByUserId(userId)).thenReturn(dummyEntity); + when(userDtoServiceMock.createDto(dummyEntity)).thenReturn(dummyUser); + + User actual = userBusinessService.getUserByAccessTokenAndUserId(accessToken, userId); + + assertEquals(dummyUser, actual); + } + + // -------------------------------------------------------------------------------------------- // + + @Test + void getUserByRefreshTokenAndUserIdWithInvalidToken() { + assertThrows(UserNotAuthenticatedException.class, () -> + userBusinessService.getUserByRefreshTokenAndUserId("", 0) + ); + } + + @Test + void getUserByRefreshTokenAndUserIdWithoutUser() { + String token = "token"; + long userId = 420; + + when(userRepositoryMock.findByRefreshTokenAndUserId(token, userId)).thenReturn(null); + + assertThrows(UserNotFoundException.class, () -> + userBusinessService.getUserByRefreshTokenAndUserId(token, userId) + ); + } + + @Test + void getUserByRefreshTokenAndUserIdCorrectly() { + String token = "token"; + long userId = 420; + User dummyUser = User.builder().id(userId).build(); + UserEntity dummyEntity = UserEntity.builder().refreshToken(token).build(); + + when(userRepositoryMock.findByRefreshTokenAndUserId(token, userId)).thenReturn(dummyEntity); + when(userDtoServiceMock.createDto(dummyEntity)).thenReturn(dummyUser); + + User actual = userBusinessService.getUserByRefreshTokenAndUserId(token, userId); + + assertEquals(dummyUser, actual); } } \ No newline at end of file diff --git a/src/test/java/de/filefighter/rest/domain/user/business/UserDtoServiceUnitTest.java b/src/test/java/de/filefighter/rest/domain/user/business/UserDtoServiceUnitTest.java new file mode 100644 index 00000000..3c5b8e3c --- /dev/null +++ b/src/test/java/de/filefighter/rest/domain/user/business/UserDtoServiceUnitTest.java @@ -0,0 +1,67 @@ +package de.filefighter.rest.domain.user.business; + +import de.filefighter.rest.domain.user.data.dto.User; +import de.filefighter.rest.domain.user.data.persistance.UserEntity; +import de.filefighter.rest.domain.user.data.persistance.UserRepository; +import de.filefighter.rest.domain.user.exceptions.UserNotFoundException; +import de.filefighter.rest.domain.user.role.GroupRepository; +import de.filefighter.rest.domain.user.role.Groups; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class UserDtoServiceUnitTest { + + private final GroupRepository groupRepositoryMock = mock(GroupRepository.class); + private final UserRepository userRepositoryMock = mock(UserRepository.class); + private UserDtoService userDtoService; + + @BeforeEach + void setUp() { + userDtoService = new UserDtoService(groupRepositoryMock, userRepositoryMock); + } + + @Test + void createDto() { + long userId = 0; + String username = "kevin"; + long[] groups = new long[]{0}; + UserEntity dummyEntity = UserEntity.builder().userId(userId).groupIds(groups).username(username).build(); + + when(groupRepositoryMock.getRolesByIds(groups)).thenReturn(new Groups[]{Groups.FAMILY}); + + User actualUser = userDtoService.createDto(dummyEntity); + assertEquals(userId, actualUser.getId()); + assertEquals(username, actualUser.getUsername()); + assertEquals(groups[0], actualUser.getGroups()[0].getGroupId()); + } + + @Test + void findEntityThrowsException() { + long userId = 0; + String username = "kevin"; + User user = User.builder().username(username).id(userId).build(); + + when(userRepositoryMock.findByUserIdAndUsername(userId, username)).thenReturn(null); + + assertThrows(UserNotFoundException.class, () -> + userDtoService.findEntity(user) + ); + } + + @Test + void findEntityWorksCorrectly() { + long userId = 0; + String username = "kevin"; + User user = User.builder().username(username).id(userId).build(); + UserEntity expected = UserEntity.builder().username(username).userId(userId).build(); + + when(userRepositoryMock.findByUserIdAndUsername(userId, username)).thenReturn(expected); + UserEntity actual = userDtoService.findEntity(user); + + assertEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/de/filefighter/rest/domain/user/rest/UserRestControllerUnitTest.java b/src/test/java/de/filefighter/rest/domain/user/rest/UserRestControllerUnitTest.java index 6d146269..42a06bd2 100644 --- a/src/test/java/de/filefighter/rest/domain/user/rest/UserRestControllerUnitTest.java +++ b/src/test/java/de/filefighter/rest/domain/user/rest/UserRestControllerUnitTest.java @@ -26,7 +26,7 @@ void setUp() { @Test void registerNewUser() { - User user = User.builder().id(420).roles(null).username("kevin").create(); + User user = User.builder().id(420).groups(null).username("kevin").build(); ResponseEntity expectedUser = new ResponseEntity<>(user, OK); when(userRestServiceMock.registerNewUserWithAccessToken(any(), any())).thenReturn(expectedUser); @@ -38,7 +38,7 @@ void registerNewUser() { @Test void loginUserWithUsernameAndPassword() { - User user = User.builder().id(420).roles(null).username("kevin").create(); + User user = User.builder().id(420).groups(null).username("kevin").build(); RefreshToken refreshToken = RefreshToken.builder().refreshToken("token").user(user).build(); ResponseEntity expectedRefreshToken = new ResponseEntity<>(refreshToken, OK); @@ -62,7 +62,7 @@ void getAccessTokenAndUserInfoByRefreshTokenAndUserId() { @Test void getUserInfoWithAccessToken() { - User user = User.builder().id(420).roles(null).username("kevin").create(); + User user = User.builder().id(420).groups(null).username("kevin").build(); ResponseEntity expectedUser = new ResponseEntity<>(user, OK); when(userRestServiceMock.getUserByAccessTokenAndUserId("token", 420)).thenReturn(expectedUser); @@ -73,9 +73,9 @@ void getUserInfoWithAccessToken() { @Test void updateUserWithAccessToken() { - User user = User.builder().id(420).roles(null).username("kevin").create(); + User user = User.builder().id(420).groups(null).username("kevin").build(); ResponseEntity expectedUser = new ResponseEntity<>(user, OK); - UserRegisterForm userRegisterForm = UserRegisterForm.builder().create(); + UserRegisterForm userRegisterForm = UserRegisterForm.builder().build(); when(userRestServiceMock.updateUserByAccessTokenAndUserId(userRegisterForm, "token", 420)).thenReturn(expectedUser); ResponseEntity actualUser = userRestController.updateUserWithAccessToken(420,"token", userRegisterForm); @@ -85,7 +85,7 @@ void updateUserWithAccessToken() { @Test void findUserByUsername(){ - User user = User.builder().id(420).roles(null).username("kevin").create(); + User user = User.builder().id(420).groups(null).username("kevin").build(); ResponseEntity expectedUser = new ResponseEntity<>(user, OK); String username="kevin"; diff --git a/src/test/resources/SystemHealth.feature b/src/test/resources/SystemHealth.feature index 40433196..1a75e9b3 100644 --- a/src/test/resources/SystemHealth.feature +++ b/src/test/resources/SystemHealth.feature @@ -7,13 +7,17 @@ Background: Scenario: SystemHealth is requested without users in db When the systemHealth endpoint is requested + And the user waits for 2 second(s) Then response contains key "userCount" and value "0" And response contains key "uptimeInSeconds" and value of at least 1 + And response status code is 200 Scenario: SystemHealth is requested with users in db Given user 1234 exists And user 3214 exists + And the user waits for 2 second(s) When the systemHealth endpoint is requested Then response contains key "userCount" and value "2" And response contains key "uptimeInSeconds" and value of at least 1 + And response status code is 200 diff --git a/src/test/resources/UserAuthorization.feature b/src/test/resources/UserAuthorization.feature index 7bc88839..d53ec677 100644 --- a/src/test/resources/UserAuthorization.feature +++ b/src/test/resources/UserAuthorization.feature @@ -1,44 +1,59 @@ -#Feature: User Authorization -# As a user -# I want to be able to log in with username and password, as well as verify my identity -# when using the endpoints. -# -#Background: -# Given database is empty -# And user with id 1234 exists and has username "user", password "secure_password" and refreshToken "token" -# -#Scenario: Successful login with username and password. -# When user requests login with username "user" and password "secure_password" -# Then response contains key "refreshToken" and value "token" -# And response status code is 200 -# And response contains the user with id 1234 -# -#Scenario: Failed login with username and password. -# When user requests login with username "user" and password "wrong_password" -# Then response contains key "message" and value "User not authenticated." -# And response contains key "status" and value "denied" -# And response status code is 401 -# -#Scenario: Successful retrieval of accessToken with refreshToken. -# When user requests accessToken with refreshToken "token" and userId 1234 -# Then response contains key "userId" and value "1234" -# And response contains valid accessToken -# And response status code is 200 -# -#Scenario: Failed retrieval of accessToken with wrong refreshToken. -# When user requests accessToken with refreshToken "not_the_token" and userId 1234 -# Then response contains key "message" and value "User not authenticated." -# And response contains key "status" and value "denied" -# And response status code is 401 -# -#Scenario: Successful UserInfo request with valid accessToken. -# Given user 1234 has access token "accessToken" -# When user requests userInfo with accessToken "accessToken" and userId 1234 -# Then response contains the user with id 1234 -# And response status code is 200 -# -#Scenario: Failed UserInfo request with invalid accessToken. -# When user requests userInfo with accessToken "notTheAccessToken" and userId 1234 -# Then response contains key "message" and value "User not authenticated." -# And response contains key "status" and value "denied" -# And response status code is 401 \ No newline at end of file +Feature: User Authorization + As a user + I want to be able to log in with username and password, as well as verify my identity + when using the endpoints. + +Background: + Given database is empty + And user with id 1234 exists and has username "user", password "secure_password" and refreshToken "token" + +Scenario: Successful login with username and password. + When user requests login with username "user" and password "secure_password" + Then response status code is 200 + And response contains refreshToken "token" and the user with id 1234 + +Scenario: Failed login with wrong username or password. + When user requests login with username "user" and password "wrong_password" + Then response contains key "message" and value "No User found with this username and password." + And response contains key "status" and value "denied" + And response status code is 404 + +Scenario: Successful creation of new accessToken with refreshToken. + When user requests accessToken with refreshToken "token" and userId 1234 + Then response contains key "userId" and value "1234" + And response contains valid accessToken for user 1234 + And response status code is 200 + +Scenario: Successful retrieval of existing accessToken with refreshToken. + Given accessToken with value "6bb9cb4f-7b51-4c0a-8013-ed7a34e56282" exists for user 1234 + When user requests accessToken with refreshToken "token" and userId 1234 + Then response contains key "userId" and value "1234" + And response contains valid accessToken for user 1234 + And response status code is 200 + + # Better scenario description? +Scenario: Successful retrieval of overwritten accessToken with refreshToken + Given accessToken with value "6bb9cb4f-7b51-4c0a-8013-ed7a34e56282" exists for user 1234 and is valid until 0 + When user requests accessToken with refreshToken "token" and userId 1234 + Then response contains key "userId" and value "1234" + And response contains valid accessToken for user 1234 with a different value than "6bb9cb4f-7b51-4c0a-8013-ed7a34e56282" + And response status code is 200 + + + Scenario: Failed retrieval of accessToken with wrong refreshToken. + When user requests accessToken with refreshToken "not_the_token" and userId 1234 + Then response contains key "message" and value "Could not find user 1234" + And response contains key "status" and value "denied" + And response status code is 404 + +Scenario: Successful UserInfo request with valid accessToken. + Given accessToken with value "6bb9cb4f-7b51-4c0a-8013-ed7a34e56282" exists for user 1234 + When user requests userInfo with accessToken "6bb9cb4f-7b51-4c0a-8013-ed7a34e56282" and userId 1234 + Then response contains the user with id 1234 + And response status code is 200 + +Scenario: Failed UserInfo request with invalid accessToken. + When user requests userInfo with accessToken "tokenInWrongFormat" and userId 1234 + Then response contains key "message" and value "User with the id 1234 could not be authenticated." + And response contains key "status" and value "denied" + And response status code is 401 \ No newline at end of file