diff --git a/authorization/pom.xml b/authorization/pom.xml index 7c89015..1c33e3d 100644 --- a/authorization/pom.xml +++ b/authorization/pom.xml @@ -28,7 +28,13 @@ nimbus-jose-jwt 9.41.1 - + + + + org.springframework.security + spring-security-oauth2-resource-server + + org.springframework.security spring-security-oauth2-jose diff --git a/authorization/src/main/java/eu/solven/kumite/account/JwtUserContextHolder.java b/authorization/src/main/java/eu/solven/kumite/account/JwtUserContextHolder.java new file mode 100644 index 0000000..7878426 --- /dev/null +++ b/authorization/src/main/java/eu/solven/kumite/account/JwtUserContextHolder.java @@ -0,0 +1,29 @@ +package eu.solven.kumite.account; + +import java.util.UUID; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; + +import eu.solven.kumite.security.LoginRouteButNotAuthenticatedException; +import reactor.core.publisher.Mono; + +public class JwtUserContextHolder implements IKumiteUserContextHolder { + + @Override + public Mono authenticatedAccountId() { + return ReactiveSecurityContextHolder.getContext().map(securityContext -> { + Authentication authentication = securityContext.getAuthentication(); + + if (authentication instanceof JwtAuthenticationToken jwtAuth) { + UUID accountId = UUID.fromString(jwtAuth.getToken().getSubject()); + + return accountId; + } else { + throw new LoginRouteButNotAuthenticatedException("Expecting a JWT token"); + } + }); + } + +} diff --git a/contest-core/pom.xml b/contest-core/pom.xml index fd3c635..c18f3b1 100644 --- a/contest-core/pom.xml +++ b/contest-core/pom.xml @@ -27,24 +27,6 @@ spring-boot-starter-webflux - - - org.springframework.security - spring-security-core - - - - - org.springframework.security - spring-security-oauth2-resource-server - - - - - org.springframework.security - spring-security-oauth2-jose - - org.springframework.boot spring-boot-starter-test diff --git a/contest-core/src/main/java/eu/solven/kumite/account/InMemoryUserRepository.java b/contest-core/src/main/java/eu/solven/kumite/account/InMemoryUserRepository.java index 463409b..2bc7c91 100644 --- a/contest-core/src/main/java/eu/solven/kumite/account/InMemoryUserRepository.java +++ b/contest-core/src/main/java/eu/solven/kumite/account/InMemoryUserRepository.java @@ -5,19 +5,17 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import org.springframework.beans.factory.InitializingBean; - -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.FakePlayer; +import eu.solven.kumite.account.fake_player.RandomPlayer; import eu.solven.kumite.player.IAccountPlayersRegistry; import eu.solven.kumite.player.KumitePlayer; import eu.solven.kumite.tools.IUuidGenerator; -import eu.solven.kumite.tools.JdkUuidGenerator; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @AllArgsConstructor @Slf4j -public class InMemoryUserRepository implements IKumiteUserRepository, IKumiteUserRawRawRepository, InitializingBean { +public class InMemoryUserRepository implements IKumiteUserRepository, IKumiteUserRawRawRepository { final Map accountIdToUser = new ConcurrentHashMap<>(); final Map accountIdToRawRaw = new ConcurrentHashMap<>(); @@ -25,15 +23,6 @@ public class InMemoryUserRepository implements IKumiteUserRepository, IKumiteUse final IAccountPlayersRegistry playersRegistry; - @Override - public void afterPropertiesSet() { - KumiteUser fakeUser = FakePlayerTokens.fakeUser(); - KumiteUserRawRaw rawRaw = fakeUser.getRaw().getRawRaw(); - - accountIdToRawRaw.put(fakeUser.getAccountId(), rawRaw); - accountIdToUser.put(rawRaw, fakeUser); - } - @Override public Optional getUser(KumiteUserRawRaw accountId) { return Optional.ofNullable(accountIdToUser.get(accountId)); @@ -56,7 +45,7 @@ public KumiteUser registerOrUpdate(KumiteUserRaw kumiteUserRaw) { return accountIdToUser.compute(rawRaw, (k, alreadyIn) -> { KumiteUser.KumiteUserBuilder kumiteUserBuilder = KumiteUser.builder().raw(kumiteUserRaw); if (alreadyIn == null) { - UUID accountId = uuidGenerator.randomUUID(); + UUID accountId = generateAccountId(rawRaw); KumitePlayer player = register(rawRaw, accountId); @@ -72,6 +61,19 @@ public KumiteUser registerOrUpdate(KumiteUserRaw kumiteUserRaw) { }); } + protected UUID generateAccountId(KumiteUserRawRaw rawRaw) { + return generateAccountId(uuidGenerator, rawRaw); + } + + public static UUID generateAccountId(IUuidGenerator uuidGenerator, KumiteUserRawRaw rawRaw) { + if (rawRaw.equals(FakePlayer.user().getRaw().getRawRaw())) { + return FakePlayer.ACCOUNT_ID; + } else if (rawRaw.equals(RandomPlayer.user().getRaw().getRawRaw())) { + return RandomPlayer.ACCOUNT_ID; + } + return uuidGenerator.randomUUID(); + } + private KumitePlayer register(KumiteUserRawRaw rawRaw, UUID accountId) { UUID playerId = playersRegistry.generateMainPlayerId(accountId); @@ -83,12 +85,4 @@ private KumitePlayer register(KumiteUserRawRaw rawRaw, UUID accountId) { return player; } - - public static InMemoryUserRepository forTests(IAccountPlayersRegistry playersRegistry) { - InMemoryUserRepository inMemoryUserRepository = - new InMemoryUserRepository(JdkUuidGenerator.INSTANCE, playersRegistry); - inMemoryUserRepository.afterPropertiesSet(); - return inMemoryUserRepository; - } - } diff --git a/contest-core/src/main/java/eu/solven/kumite/board/BoardsRegistry.java b/contest-core/src/main/java/eu/solven/kumite/board/BoardsRegistry.java index 75d740d..c6919b9 100644 --- a/contest-core/src/main/java/eu/solven/kumite/board/BoardsRegistry.java +++ b/contest-core/src/main/java/eu/solven/kumite/board/BoardsRegistry.java @@ -31,4 +31,10 @@ public IHasBoard makeDynamicBoardHolder(UUID contestId) { public void updateBoard(UUID contestId, IKumiteBoard currentBoard) { boardRepository.updateBoard(contestId, currentBoard); } + + public void forceGameover(UUID contestId) { +// currentBoard. + // TODO Auto-generated method stub + + } } diff --git a/contest-core/src/main/java/eu/solven/kumite/contest/ActiveContestGenerator.java b/contest-core/src/main/java/eu/solven/kumite/contest/ActiveContestGenerator.java index ba9f25d..4c00c21 100644 --- a/contest-core/src/main/java/eu/solven/kumite/contest/ActiveContestGenerator.java +++ b/contest-core/src/main/java/eu/solven/kumite/contest/ActiveContestGenerator.java @@ -5,7 +5,7 @@ import java.util.UUID; import java.util.random.RandomGenerator; -import eu.solven.kumite.account.KumiteUser; +import eu.solven.kumite.account.fake_player.RandomPlayer; import eu.solven.kumite.app.IKumiteSpringProfiles; import eu.solven.kumite.board.IKumiteBoard; import eu.solven.kumite.game.GameSearchParameters; @@ -51,7 +51,7 @@ public void makeContestsIfNoneJoinable() { String contestName = "Auto-generated " + randomGenerator.nextInt(128); ContestCreationMetadata constantMetadata = ContestCreationMetadata.fromGame(gameMetadata) .name(contestName) - .author(KumiteUser.SERVER_ACCOUNTID) + .author(RandomPlayer.ACCOUNT_ID) .build(); Contest contest = contestsRegistry.registerContest(game, constantMetadata, initialBoard); diff --git a/contest-core/src/main/java/eu/solven/kumite/contest/ContestHandler.java b/contest-core/src/main/java/eu/solven/kumite/contest/ContestHandler.java index fb1ef7a..883b7d4 100644 --- a/contest-core/src/main/java/eu/solven/kumite/contest/ContestHandler.java +++ b/contest-core/src/main/java/eu/solven/kumite/contest/ContestHandler.java @@ -7,15 +7,13 @@ import java.util.stream.Collectors; import org.springframework.http.MediaType; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import com.fasterxml.jackson.databind.ObjectMapper; +import eu.solven.kumite.account.IKumiteUserContextHolder; import eu.solven.kumite.app.KumiteJackson; import eu.solven.kumite.app.webflux.api.KumiteHandlerHelper; import eu.solven.kumite.board.IKumiteBoard; @@ -23,7 +21,6 @@ import eu.solven.kumite.game.GameMetadata; import eu.solven.kumite.game.GamesRegistry; import eu.solven.kumite.game.IGame; -import eu.solven.kumite.security.LoginRouteButNotAuthenticatedException; import io.micrometer.common.util.StringUtils; import lombok.Builder; import lombok.NonNull; @@ -47,6 +44,9 @@ public class ContestHandler { @NonNull final RandomGenerator randomGenerator; + @NonNull + final IKumiteUserContextHolder kumiteUser; + public Mono listContests(ServerRequest request) { ContestSearchParametersBuilder parameters = ContestSearchParameters.builder(); @@ -74,23 +74,11 @@ public Mono listContests(ServerRequest request) { } // BEWARE we coupled the generation of a contest and its board. This may be poor design. - public Mono generateContest(ServerRequest request) { + public Mono openContest(ServerRequest request) { UUID gameId = KumiteHandlerHelper.uuid(request, "game_id"); IGame game = gamesRegistry.getGame(gameId); - return ReactiveSecurityContextHolder.getContext().map(securityContext -> { - Authentication authentication = securityContext.getAuthentication(); - - if (authentication instanceof JwtAuthenticationToken jwtAuth) { - // jwtAuth.ge - - UUID accountId = UUID.fromString(jwtAuth.getToken().getSubject()); - - return accountId; - } else { - throw new LoginRouteButNotAuthenticatedException("Expecting a JWT token"); - } - }).flatMap(authorAccountId -> { + return kumiteUser.authenticatedAccountId().flatMap(authorAccountId -> { return request.bodyToMono(Map.class).flatMap(contestBody -> { Map rawConstantMetadata = (Map) contestBody.get("constant_metadata"); @@ -151,4 +139,25 @@ private ContestCreationMetadata validateConstantMetadata(UUID authorAccountId, return mergedContestMetadata; } + public Mono deleteContest(ServerRequest request) { + UUID contestId = KumiteHandlerHelper.uuid(request, "contest_id"); + + return kumiteUser.authenticatedAccountId().flatMap(authorAccountId -> { + ContestCreationMetadata contest = contestsRegistry.contestsRepository.getById(contestId) + .orElseThrow(() -> new IllegalArgumentException("There is no contest for contestId=" + contestId)); + + if (!contest.getAuthor().equals(authorAccountId)) { + throw new IllegalArgumentException("Can not DELETE contestId=%s as its author is not you (but %s)" + .formatted(contestId, contest.getAuthor())); + } + + contestsRegistry.deleteContest(contestId); + + return ServerResponse.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(Map.of("contestId", contestId, "author", authorAccountId))); + }); + + } + } \ No newline at end of file diff --git a/contest-core/src/main/java/eu/solven/kumite/contest/ContestsRegistry.java b/contest-core/src/main/java/eu/solven/kumite/contest/ContestsRegistry.java index 45ad100..209ca51 100644 --- a/contest-core/src/main/java/eu/solven/kumite/contest/ContestsRegistry.java +++ b/contest-core/src/main/java/eu/solven/kumite/contest/ContestsRegistry.java @@ -127,4 +127,10 @@ public List searchContests(ContestSearchParameters search) { return metaStream.collect(Collectors.toList()); } + + public void deleteContest(UUID contestId) { + boardsRegistry.forceGameover(contestId); + contestPlayersRegistry.forceGameover(contestId); + contestsRepository.archive(contestId); + } } diff --git a/contest-core/src/main/java/eu/solven/kumite/contest/persistence/IContestsRepository.java b/contest-core/src/main/java/eu/solven/kumite/contest/persistence/IContestsRepository.java index bcb71a7..0c7f7db 100644 --- a/contest-core/src/main/java/eu/solven/kumite/contest/persistence/IContestsRepository.java +++ b/contest-core/src/main/java/eu/solven/kumite/contest/persistence/IContestsRepository.java @@ -27,4 +27,6 @@ public interface IContestsRepository { Stream> stream(); + void archive(UUID contestId); + } diff --git a/contest-core/src/main/java/eu/solven/kumite/contest/persistence/InMemoryContestRepository.java b/contest-core/src/main/java/eu/solven/kumite/contest/persistence/InMemoryContestRepository.java index a85dc8e..3d626ea 100644 --- a/contest-core/src/main/java/eu/solven/kumite/contest/persistence/InMemoryContestRepository.java +++ b/contest-core/src/main/java/eu/solven/kumite/contest/persistence/InMemoryContestRepository.java @@ -36,4 +36,9 @@ public void clear() { log.info("We reset {} contests", size); uuidToContests.clear(); } + + @Override + public void archive(UUID contestId) { + // TODO Should we remove the contest? + } } diff --git a/contest-core/src/main/java/eu/solven/kumite/player/ContendersFromBoard.java b/contest-core/src/main/java/eu/solven/kumite/player/ContendersFromBoard.java index 7f95268..1f6d5a9 100644 --- a/contest-core/src/main/java/eu/solven/kumite/player/ContendersFromBoard.java +++ b/contest-core/src/main/java/eu/solven/kumite/player/ContendersFromBoard.java @@ -1,5 +1,6 @@ package eu.solven.kumite.player; +import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -51,4 +52,13 @@ public IHasPlayers makeDynamicHasPlayers(UUID contestId) { }).collect(Collectors.toList()); } + @Override + public void gameover(UUID contestId) { + Optional board = boardRepository.getBoard(contestId); + if (board.isPresent()) { + // TODO Checkif the board is actually over + // board.get(). + } + } + } diff --git a/contest-core/src/main/java/eu/solven/kumite/player/ContestPlayersRegistry.java b/contest-core/src/main/java/eu/solven/kumite/player/ContestPlayersRegistry.java index 8a49470..21ceeac 100644 --- a/contest-core/src/main/java/eu/solven/kumite/player/ContestPlayersRegistry.java +++ b/contest-core/src/main/java/eu/solven/kumite/player/ContestPlayersRegistry.java @@ -166,4 +166,9 @@ public PlayerContestStatus getPlayingPlayer(UUID playerId, Contest contestMetada return playingPlayer; } + + public void forceGameover(UUID contestId) { + contestToViewingAccounts.remove(contestId); + contestPlayersRepository.gameover(contestId); + } } diff --git a/contest-core/src/main/java/eu/solven/kumite/player/IContendersRepository.java b/contest-core/src/main/java/eu/solven/kumite/player/IContendersRepository.java index 71e7f3b..f2c0651 100644 --- a/contest-core/src/main/java/eu/solven/kumite/player/IContendersRepository.java +++ b/contest-core/src/main/java/eu/solven/kumite/player/IContendersRepository.java @@ -24,4 +24,6 @@ public interface IContendersRepository { IHasPlayers makeDynamicHasPlayers(UUID contestId); + void gameover(UUID contestId); + } diff --git a/contest-core/src/main/java/eu/solven/kumite/player/InMemoryContendersRepository.java b/contest-core/src/main/java/eu/solven/kumite/player/InMemoryContendersRepository.java index d76e8ed..fb05a47 100644 --- a/contest-core/src/main/java/eu/solven/kumite/player/InMemoryContendersRepository.java +++ b/contest-core/src/main/java/eu/solven/kumite/player/InMemoryContendersRepository.java @@ -43,4 +43,9 @@ public boolean isContender(UUID contestId, UUID playerId) { return viewContenders(contestId).stream().anyMatch(somePlayerId -> somePlayerId.equals(playerId)); } + @Override + public void gameover(UUID contestId) { + contestToContenders.remove(contestId); + } + } diff --git a/contest-core/src/main/java/eu/solven/kumite/player/persistence/BijectiveAccountPlayersRegistry.java b/contest-core/src/main/java/eu/solven/kumite/player/persistence/BijectiveAccountPlayersRegistry.java index 28004ed..6c4e15c 100644 --- a/contest-core/src/main/java/eu/solven/kumite/player/persistence/BijectiveAccountPlayersRegistry.java +++ b/contest-core/src/main/java/eu/solven/kumite/player/persistence/BijectiveAccountPlayersRegistry.java @@ -5,7 +5,8 @@ import java.util.List; import java.util.UUID; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.FakePlayer; +import eu.solven.kumite.account.fake_player.RandomPlayer; import eu.solven.kumite.player.IAccountPlayersRegistry; import eu.solven.kumite.player.IHasPlayers; import eu.solven.kumite.player.KumitePlayer; @@ -25,8 +26,10 @@ public final class BijectiveAccountPlayersRegistry implements IAccountPlayersReg public void registerPlayer(KumitePlayer player) { UUID accountId = player.getAccountId(); UUID playerId = player.getPlayerId(); - if (FakePlayerTokens.FAKE_ACCOUNT_ID.equals(accountId) && FakePlayerTokens.isFakePlayer(playerId)) { + if (FakePlayer.ACCOUNT_ID.equals(accountId) && FakePlayer.isFakePlayer(playerId)) { log.info("Registering the fakeUser"); + } else if (RandomPlayer.ACCOUNT_ID.equals(accountId) && RandomPlayer.isRandomPlayer(playerId)) { + log.info("Registering the randomuser"); } else if (playerId.equals(generateMainPlayerId(accountId))) { log.info("Registering accountId={} playerId={}", accountId, playerId); } else { @@ -36,8 +39,10 @@ public void registerPlayer(KumitePlayer player) { @Override public UUID getAccountId(UUID playerId) { - if (FakePlayerTokens.isFakePlayer(playerId)) { - return FakePlayerTokens.FAKE_ACCOUNT_ID; + if (FakePlayer.isFakePlayer(playerId)) { + return FakePlayer.ACCOUNT_ID; + } else if (RandomPlayer.isRandomPlayer(playerId)) { + return RandomPlayer.ACCOUNT_ID; } return accountIdGivenPlayerId(playerId); @@ -45,8 +50,11 @@ public UUID getAccountId(UUID playerId) { @Override public IHasPlayers makeDynamicHasPlayers(UUID accountId) { - if (FakePlayerTokens.FAKE_ACCOUNT_ID.equals(accountId)) { - List players = Arrays.asList(FakePlayerTokens.fakePlayer(0), FakePlayerTokens.fakePlayer(1)); + if (FakePlayer.ACCOUNT_ID.equals(accountId)) { + List players = Arrays.asList(FakePlayer.fakePlayer(0), FakePlayer.fakePlayer(1)); + return () -> players; + } else if (RandomPlayer.ACCOUNT_ID.equals(accountId)) { + List players = Arrays.asList(RandomPlayer.randomPlayer(0), RandomPlayer.randomPlayer(1)); return () -> players; } @@ -58,8 +66,10 @@ public IHasPlayers makeDynamicHasPlayers(UUID accountId) { @Override public UUID generateMainPlayerId(UUID accountId) { - if (FakePlayerTokens.FAKE_ACCOUNT_ID.equals(accountId)) { - return FakePlayerTokens.FAKE_PLAYER_ID1; + if (FakePlayer.ACCOUNT_ID.equals(accountId)) { + return FakePlayer.PLAYER_ID1; + } else if (RandomPlayer.ACCOUNT_ID.equals(accountId)) { + return RandomPlayer.PLAYERID_1; } return playerIdGivenAccountId(accountId); diff --git a/contest-core/src/main/java/eu/solven/kumite/player/persistence/InMemoryAccountPlayersRegistry.java b/contest-core/src/main/java/eu/solven/kumite/player/persistence/InMemoryAccountPlayersRegistry.java index 332e81e..cf022d5 100644 --- a/contest-core/src/main/java/eu/solven/kumite/player/persistence/InMemoryAccountPlayersRegistry.java +++ b/contest-core/src/main/java/eu/solven/kumite/player/persistence/InMemoryAccountPlayersRegistry.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentSkipListSet; import java.util.stream.Collectors; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.FakePlayer; +import eu.solven.kumite.account.fake_player.RandomPlayer; import eu.solven.kumite.player.IAccountPlayersRegistry; import eu.solven.kumite.player.IHasPlayers; import eu.solven.kumite.player.KumitePlayer; @@ -27,9 +28,6 @@ public final class InMemoryAccountPlayersRegistry implements IAccountPlayersRegi public InMemoryAccountPlayersRegistry(IUuidGenerator uuidGenerator) { this.uuidGenerator = uuidGenerator; - - registerPlayer(FakePlayerTokens.fakePlayer()); - registerPlayer(FakePlayerTokens.fakePlayer(1)); } @Override @@ -50,10 +48,6 @@ public void registerPlayer(KumitePlayer player) { @Override public UUID getAccountId(UUID playerId) { - if (FakePlayerTokens.isFakePlayer(playerId)) { - return FakePlayerTokens.FAKE_ACCOUNT_ID; - } - UUID accountId = playerIdToAccountId.get(playerId); if (accountId == null) { throw new IllegalArgumentException("Unknown playerId: " + playerId); @@ -72,6 +66,12 @@ public IHasPlayers makeDynamicHasPlayers(UUID accountId) { @Override public UUID generateMainPlayerId(UUID accountId) { + if (FakePlayer.ACCOUNT_ID.equals(accountId)) { + return FakePlayer.PLAYER_ID1; + } else if (RandomPlayer.ACCOUNT_ID.equals(accountId)) { + return RandomPlayer.PLAYERID_1; + } + return uuidGenerator.randomUUID(); } } diff --git a/contest-core/src/test/java/eu/solven/kumite/player/TestBijectiveAccountPlayersRegistry.java b/contest-core/src/test/java/eu/solven/kumite/player/TestBijectiveAccountPlayersRegistry.java index 6a4cdca..79bb52b 100644 --- a/contest-core/src/test/java/eu/solven/kumite/player/TestBijectiveAccountPlayersRegistry.java +++ b/contest-core/src/test/java/eu/solven/kumite/player/TestBijectiveAccountPlayersRegistry.java @@ -6,7 +6,7 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.FakePlayer; import eu.solven.kumite.player.persistence.BijectiveAccountPlayersRegistry; public class TestBijectiveAccountPlayersRegistry { @@ -14,10 +14,10 @@ public class TestBijectiveAccountPlayersRegistry { @Test public void testFakePlayer() { - Assertions.assertThat(playersRegistry.getAccountId(FakePlayerTokens.FAKE_PLAYER_ID1)) - .isEqualTo(FakePlayerTokens.FAKE_ACCOUNT_ID); - Assertions.assertThat(playersRegistry.getAccountId(FakePlayerTokens.FAKE_PLAYER_ID2)) - .isEqualTo(FakePlayerTokens.FAKE_ACCOUNT_ID); + Assertions.assertThat(playersRegistry.getAccountId(FakePlayer.PLAYER_ID1)) + .isEqualTo(FakePlayer.ACCOUNT_ID); + Assertions.assertThat(playersRegistry.getAccountId(FakePlayer.PLAYER_ID2)) + .isEqualTo(FakePlayer.ACCOUNT_ID); } @Test @@ -38,16 +38,16 @@ public void testNormalPlayer() { @Test public void testHasPlayers_FakePlayers() { List players = - playersRegistry.makeDynamicHasPlayers(FakePlayerTokens.FAKE_ACCOUNT_ID).getPlayers(); + playersRegistry.makeDynamicHasPlayers(FakePlayer.ACCOUNT_ID).getPlayers(); Assertions.assertThat(players) .contains(KumitePlayer.builder() - .playerId(FakePlayerTokens.FAKE_PLAYER_ID1) - .accountId(FakePlayerTokens.FAKE_ACCOUNT_ID) + .playerId(FakePlayer.PLAYER_ID1) + .accountId(FakePlayer.ACCOUNT_ID) .build()) .contains(KumitePlayer.builder() - .playerId(FakePlayerTokens.FAKE_PLAYER_ID2) - .accountId(FakePlayerTokens.FAKE_ACCOUNT_ID) + .playerId(FakePlayer.PLAYER_ID2) + .accountId(FakePlayer.ACCOUNT_ID) .build()) .hasSize(2); } diff --git a/contest-core/src/test/java/eu/solven/kumite/player/TestInMemoryAccountPlayersRegistry.java b/contest-core/src/test/java/eu/solven/kumite/player/TestInMemoryAccountPlayersRegistry.java index 50e382a..373a25f 100644 --- a/contest-core/src/test/java/eu/solven/kumite/player/TestInMemoryAccountPlayersRegistry.java +++ b/contest-core/src/test/java/eu/solven/kumite/player/TestInMemoryAccountPlayersRegistry.java @@ -5,34 +5,38 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.FakePlayer; import eu.solven.kumite.player.persistence.InMemoryAccountPlayersRegistry; import eu.solven.kumite.tools.JdkUuidGenerator; public class TestInMemoryAccountPlayersRegistry { IAccountPlayersRegistry playersRegistry = new InMemoryAccountPlayersRegistry(new JdkUuidGenerator()); + private void registerFakePlayers() { + playersRegistry.registerPlayer(FakePlayer.fakePlayer()); + playersRegistry.registerPlayer(FakePlayer.fakePlayer(1)); + } + @Test public void testFakePlayer() { - Assertions.assertThat(playersRegistry.getAccountId(FakePlayerTokens.FAKE_PLAYER_ID1)) - .isEqualTo(FakePlayerTokens.FAKE_ACCOUNT_ID); - Assertions.assertThat(playersRegistry.getAccountId(FakePlayerTokens.FAKE_PLAYER_ID2)) - .isEqualTo(FakePlayerTokens.FAKE_ACCOUNT_ID); + registerFakePlayers(); + + Assertions.assertThat(playersRegistry.getAccountId(FakePlayer.PLAYER_ID1)).isEqualTo(FakePlayer.ACCOUNT_ID); + Assertions.assertThat(playersRegistry.getAccountId(FakePlayer.PLAYER_ID2)).isEqualTo(FakePlayer.ACCOUNT_ID); } @Test public void testHasPlayers_FakePlayers() { - List players = - playersRegistry.makeDynamicHasPlayers(FakePlayerTokens.FAKE_ACCOUNT_ID).getPlayers(); + registerFakePlayers(); + + List players = playersRegistry.makeDynamicHasPlayers(FakePlayer.ACCOUNT_ID).getPlayers(); Assertions.assertThat(players) + .contains( + KumitePlayer.builder().playerId(FakePlayer.PLAYER_ID1).accountId(FakePlayer.ACCOUNT_ID).build()) .contains(KumitePlayer.builder() - .playerId(FakePlayerTokens.FAKE_PLAYER_ID1) - .accountId(FakePlayerTokens.FAKE_ACCOUNT_ID) - .build()) - .contains(KumitePlayer.builder() - .playerId(FakePlayerTokens.FAKE_PLAYER_ID2) - .accountId(FakePlayerTokens.FAKE_ACCOUNT_ID) + .playerId(FakePlayer.PLAYER_ID2) + .accountId(FakePlayer.ACCOUNT_ID) .build()) .hasSize(2); } diff --git a/js/src/main/resources/static/ui/js/kumite-contest-delete.js b/js/src/main/resources/static/ui/js/kumite-contest-delete.js new file mode 100644 index 0000000..e61b995 --- /dev/null +++ b/js/src/main/resources/static/ui/js/kumite-contest-delete.js @@ -0,0 +1,38 @@ +import { ref, onMounted, onUnmounted } from "vue"; + +import { mapState } from "pinia"; +import { useKumiteStore } from "./store.js"; + +export default { + props: { + contestId: { + type: String, + required: true, + }, + gameId: { + type: String, + required: true, + }, + }, + computed: { + ...mapState(useKumiteStore, ["nbGameFetching", "nbContestFetching"]), + ...mapState(useKumiteStore, { + game(store) { + return store.games[this.gameId]; + }, + contest(store) { + return store.contests[this.contestId]; + }, + }), + }, + setup(props) { + const store = useKumiteStore(); + + store.loadContestIfMissing(props.gameId, props.contestId); + + return {}; + }, + template: /* HTML */ ` + + `, +}; diff --git a/js/src/main/resources/static/ui/js/kumite-contest-header.js b/js/src/main/resources/static/ui/js/kumite-contest-header.js index 1de9a91..6806d36 100644 --- a/js/src/main/resources/static/ui/js/kumite-contest-header.js +++ b/js/src/main/resources/static/ui/js/kumite-contest-header.js @@ -4,10 +4,12 @@ import { mapState } from "pinia"; import { useKumiteStore } from "./store.js"; import KumiteAccountRef from "./kumite-account-ref.js"; +import KumiteContestDelete from "./kumite-contest-delete.js"; export default { components: { KumiteAccountRef, + KumiteContestDelete, }, props: { contestId: { @@ -93,6 +95,9 @@ export default {
  • author:
  • created: {{contest.constantMetadata.created}}
  • +
  • + +
`, diff --git a/monolith/src/test/java/eu/solven/kumite/app/it/TestRandomGamingLogic.java b/monolith/src/test/java/eu/solven/kumite/app/it/TestRandomGamingLogic.java index ae48aed..bf7e61e 100644 --- a/monolith/src/test/java/eu/solven/kumite/app/it/TestRandomGamingLogic.java +++ b/monolith/src/test/java/eu/solven/kumite/app/it/TestRandomGamingLogic.java @@ -20,7 +20,7 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.RandomPlayer; import eu.solven.kumite.app.IKumiteSpringProfiles; import eu.solven.kumite.app.KumiteContestServerApplication; import eu.solven.kumite.app.KumiteWebclientServerProperties; @@ -42,7 +42,7 @@ @ExtendWith(SpringExtension.class) @SpringBootTest(classes = KumiteContestServerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles({ IKumiteSpringProfiles.P_UNSAFE, IKumiteSpringProfiles.P_INMEMORY, IKumiteSpringProfiles.P_FAKEUSER }) +@ActiveProfiles({ IKumiteSpringProfiles.P_UNSAFE, IKumiteSpringProfiles.P_INMEMORY }) @TestPropertySource(properties = { "kumite.random.seed=123", "kumite.player.wait_duration_if_no_move" + "=PT0.001S", @@ -59,7 +59,7 @@ public class TestRandomGamingLogic { @Test public void testOptimization() { - UUID playerId = FakePlayerTokens.FAKE_PLAYER_ID1; + UUID playerId = RandomPlayer.PLAYERID_1; KumiteWebclientServerProperties properties = KumiteWebclientServerProperties.forTests(env, randomServerPort); KumiteWebclientServer kumiteServer = KumiteWebclientServer.fromProperties(properties); @@ -93,7 +93,7 @@ public void test1v1TurnBased() throws InterruptedException { IGamingLogic kumitePlayer = new RandomGamingLogic(env, kumiteServer); for (int iPlayer = 0; iPlayer < nbPlayers; iPlayer++) { - UUID playerId = FakePlayerTokens.fakePlayerId(iPlayer); + UUID playerId = RandomPlayer.randomPlayerId(iPlayer); executorService.execute(() -> { try { diff --git a/monolith/src/test/java/eu/solven/kumite/app/it/TestRandomGamingLogicRedis.java b/monolith/src/test/java/eu/solven/kumite/app/it/TestRandomGamingLogicRedis.java index db31ff7..6543d15 100644 --- a/monolith/src/test/java/eu/solven/kumite/app/it/TestRandomGamingLogicRedis.java +++ b/monolith/src/test/java/eu/solven/kumite/app/it/TestRandomGamingLogicRedis.java @@ -21,7 +21,7 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.RandomPlayer; import eu.solven.kumite.app.IKumiteSpringProfiles; import eu.solven.kumite.app.KumiteContestServerApplication; import eu.solven.kumite.app.KumiteWebclientServerProperties; @@ -44,7 +44,7 @@ @ExtendWith(SpringExtension.class) @SpringBootTest(classes = KumiteContestServerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles({ IKumiteSpringProfiles.P_UNSAFE, IKumiteSpringProfiles.P_REDIS, IKumiteSpringProfiles.P_FAKEUSER }) +@ActiveProfiles({ IKumiteSpringProfiles.P_UNSAFE, IKumiteSpringProfiles.P_REDIS }) @TestPropertySource(properties = { "kumite.random.seed=123", "kumite.player.wait_duration_if_no_move" + "=PT0.001S", KumiteWebclientServerProperties.KEY_PLAYER_CONTESTBASEURL + "=http://localhost:LocalServerPort", @@ -65,7 +65,7 @@ public class TestRandomGamingLogicRedis { @Test public void testOptimization() { - UUID playerId = FakePlayerTokens.FAKE_PLAYER_ID1; + UUID playerId = RandomPlayer.PLAYERID_1; KumiteWebclientServerProperties properties = KumiteWebclientServerProperties.forTests(env, randomServerPort); IKumiteServer kumiteServer = KumiteWebclientServer.fromProperties(properties); @@ -96,7 +96,7 @@ public void test1v1TurnBased() throws InterruptedException { IGamingLogic kumitePlayer = new RandomGamingLogic(env, kumiteServer); for (int iPlayer = 0; iPlayer < nbPlayers; iPlayer++) { - UUID playerId = FakePlayerTokens.fakePlayerId(iPlayer); + UUID playerId = RandomPlayer.randomPlayerId(iPlayer); executorService.execute(() -> { try { diff --git a/monolith/src/test/java/eu/solven/kumite/app/it/TestTSPLifecycleThroughRouter.java b/monolith/src/test/java/eu/solven/kumite/app/it/TestTSPLifecycleThroughRouter.java index 4272eb4..477d680 100644 --- a/monolith/src/test/java/eu/solven/kumite/app/it/TestTSPLifecycleThroughRouter.java +++ b/monolith/src/test/java/eu/solven/kumite/app/it/TestTSPLifecycleThroughRouter.java @@ -14,7 +14,7 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.RandomPlayer; import eu.solven.kumite.app.IKumiteSpringProfiles; import eu.solven.kumite.app.KumiteContestServerApplication; import eu.solven.kumite.app.KumiteWebclientServerProperties; @@ -37,7 +37,7 @@ @ExtendWith(SpringExtension.class) @SpringBootTest(classes = KumiteContestServerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles({ IKumiteSpringProfiles.P_UNSAFE, IKumiteSpringProfiles.P_INMEMORY, IKumiteSpringProfiles.P_FAKEUSER }) +@ActiveProfiles({ IKumiteSpringProfiles.P_UNSAFE, IKumiteSpringProfiles.P_INMEMORY }) @TestPropertySource(properties = { "kumite.random.seed=123", "kumite.player.wait_duration_if_no_move" + "=PT0.001S", KumiteWebclientServerProperties.KEY_PLAYER_CONTESTBASEURL + "=http://localhost:LocalServerPort" }) @@ -53,7 +53,7 @@ public class TestTSPLifecycleThroughRouter { @Test public void testSinglePlayer() { - UUID playerId = FakePlayerTokens.FAKE_PLAYER_ID1; + UUID playerId = RandomPlayer.PLAYERID_1; KumiteWebclientServerProperties properties = KumiteWebclientServerProperties.forTests(env, randomServerPort); IKumiteServer kumiteServer = KumiteWebclientServer.fromProperties(properties); diff --git a/player/src/main/java/eu/solven/kumite/app/KumiteWebclientServerProperties.java b/player/src/main/java/eu/solven/kumite/app/KumiteWebclientServerProperties.java index 89d909b..c914f62 100644 --- a/player/src/main/java/eu/solven/kumite/app/KumiteWebclientServerProperties.java +++ b/player/src/main/java/eu/solven/kumite/app/KumiteWebclientServerProperties.java @@ -3,7 +3,8 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.FakePlayer; +import eu.solven.kumite.account.fake_player.RandomPlayer; import eu.solven.kumite.login.RefreshTokenWrapper; import eu.solven.kumite.oauth2.authorizationserver.KumiteTokenService; import eu.solven.kumite.tools.IUuidGenerator; @@ -20,6 +21,7 @@ public class KumiteWebclientServerProperties { public static final String ENV_REFRESH_TOKEN = "kumite.player.refresh_token"; public static final String PLACEHOLDER_GENERATEFAKEPLAYER = "GENERATE_FAKEUSER"; + public static final String PLACEHOLDER_GENERATERANDOMPLAYER = "GENERATE_RANDOMUSER"; String baseUrl; String refreshToken; @@ -36,8 +38,16 @@ public static String loadRefreshToken(Environment env, IUuidGenerator uuidGenera log.info("Generating on-the-fly a fakeUser refreshToken"); } KumiteTokenService kumiteTokenService = new KumiteTokenService(env, uuidGenerator); - RefreshTokenWrapper wrappedRefreshToken = kumiteTokenService - .wrapInJwtRefreshToken(FakePlayerTokens.fakeUser(), FakePlayerTokens.fakePlayers()); + RefreshTokenWrapper wrappedRefreshToken = + kumiteTokenService.wrapInJwtRefreshToken(FakePlayer.user(), FakePlayer.fakePlayers()); + refreshToken = wrappedRefreshToken.getRefreshToken(); + } else if (KumiteWebclientServerProperties.PLACEHOLDER_GENERATERANDOMPLAYER.equals(refreshToken)) { + { + log.info("Generating on-the-fly a fakeUser refreshToken"); + } + KumiteTokenService kumiteTokenService = new KumiteTokenService(env, uuidGenerator); + RefreshTokenWrapper wrappedRefreshToken = + kumiteTokenService.wrapInJwtRefreshToken(RandomPlayer.user(), RandomPlayer.randomPlayers()); refreshToken = wrappedRefreshToken.getRefreshToken(); } return refreshToken; @@ -46,7 +56,7 @@ public static String loadRefreshToken(Environment env, IUuidGenerator uuidGenera public static KumiteWebclientServerProperties forTests(Environment env, int randomServerPort) { String refreshToken = loadRefreshToken(env, JdkUuidGenerator.INSTANCE, - KumiteWebclientServerProperties.PLACEHOLDER_GENERATEFAKEPLAYER); + KumiteWebclientServerProperties.PLACEHOLDER_GENERATERANDOMPLAYER); // https://github.com/spring-projects/spring-boot/issues/5077 String baseUrl = env.getRequiredProperty(KEY_PLAYER_CONTESTBASEURL) diff --git a/player/src/test/java/eu/solven/kumite/app/TestParseFakePlayer.java b/player/src/test/java/eu/solven/kumite/app/TestParseFakePlayer.java index 0b86686..1b362f7 100644 --- a/player/src/test/java/eu/solven/kumite/app/TestParseFakePlayer.java +++ b/player/src/test/java/eu/solven/kumite/app/TestParseFakePlayer.java @@ -13,6 +13,7 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; +import eu.solven.kumite.account.fake_player.RandomPlayer; import lombok.extern.slf4j.Slf4j; @ExtendWith(SpringExtension.class) @@ -21,11 +22,11 @@ // Enables generation on-the-fly on refreshToken IKumiteSpringProfiles.P_UNSAFE_OAUTH2, // Enables playing as fakeUser - IKumiteSpringProfiles.P_FAKEUSER, + // IKumiteSpringProfiles.P_FAKEUSER, IKumiteSpringProfiles.P_UNSAFE_PLAYER }) @TestPropertySource(properties = { KumiteWebclientServerProperties.KEY_PLAYER_CONTESTBASEURL + "=someUrl", KumiteWebclientServerProperties.ENV_REFRESH_TOKEN + "=" - + KumiteWebclientServerProperties.PLACEHOLDER_GENERATEFAKEPLAYER }) + + KumiteWebclientServerProperties.PLACEHOLDER_GENERATERANDOMPLAYER }) @Slf4j public class TestParseFakePlayer implements IKumiteSpringProfiles { @@ -43,8 +44,8 @@ public void testPlayerIdFromAccessToken() { Set playerIds = conf.playerIdFromRefreshToken(kumiteWebclientServerProperties); Assertions.assertThat(playerIds) - .contains(UUID.fromString("11111111-1111-1111-1111-111111111111")) - .contains(UUID.fromString("11111111-1111-1111-1111-222222222222")) + .contains(RandomPlayer.randomPlayer(0).getPlayerId()) + .contains(RandomPlayer.randomPlayer(1).getPlayerId()) .hasSize(2); } } diff --git a/public/src/main/java/eu/solven/kumite/account/KumiteUser.java b/public/src/main/java/eu/solven/kumite/account/KumiteUser.java index fb4ea57..383a879 100644 --- a/public/src/main/java/eu/solven/kumite/account/KumiteUser.java +++ b/public/src/main/java/eu/solven/kumite/account/KumiteUser.java @@ -21,9 +21,6 @@ @Jacksonized @Slf4j public class KumiteUser { - // Used to create contests - public static final UUID SERVER_ACCOUNTID = UUID.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"); - // Multiple Users may be attached to the same account (e.g. by using different OAuth2 providers) @NonNull UUID accountId; diff --git a/public/src/main/java/eu/solven/kumite/account/fake_player/FakePlayerTokens.java b/public/src/main/java/eu/solven/kumite/account/fake_player/FakePlayer.java similarity index 61% rename from public/src/main/java/eu/solven/kumite/account/fake_player/FakePlayerTokens.java rename to public/src/main/java/eu/solven/kumite/account/fake_player/FakePlayer.java index 1091877..bf43709 100644 --- a/public/src/main/java/eu/solven/kumite/account/fake_player/FakePlayerTokens.java +++ b/public/src/main/java/eu/solven/kumite/account/fake_player/FakePlayer.java @@ -17,52 +17,52 @@ * */ @Slf4j -public class FakePlayerTokens { +public class FakePlayer { // IKumiteSpringProfiles.P_DEFAULT_FAKE_USER - public static final UUID FAKE_ACCOUNT_ID = UUID.fromString("11111111-1111-1111-1111-000000000000"); - public static final UUID FAKE_PLAYER_ID1 = UUID.fromString("11111111-1111-1111-1111-111111111111"); - public static final UUID FAKE_PLAYER_ID2 = UUID.fromString("11111111-1111-1111-1111-222222222222"); + public static final UUID ACCOUNT_ID = UUID.fromString("11111111-1111-1111-1111-000000000000"); + public static final UUID PLAYER_ID1 = UUID.fromString("11111111-1111-1111-1111-111111111111"); + public static final UUID PLAYER_ID2 = UUID.fromString("11111111-1111-1111-1111-222222222222"); public static UUID fakePlayerId(int playerIndex) { if (playerIndex == 0) { - return FAKE_PLAYER_ID1; + return PLAYER_ID1; } else if (playerIndex == 1) { - return FAKE_PLAYER_ID2; + return PLAYER_ID2; } else { throw new IllegalArgumentException("There is no fakePlayer for playerIndex=" + playerIndex); } } public static boolean isFakePlayer(UUID playerId) { - if (FAKE_PLAYER_ID1.equals(playerId) || FAKE_PLAYER_ID2.equals(playerId)) { + if (PLAYER_ID1.equals(playerId) || PLAYER_ID2.equals(playerId)) { return true; } else { return false; } } - public static KumiteUser fakeUser() { - KumiteUserRawRaw rawRaw = KumiteUserRawRaw.builder().providerId("fakeProviderId").sub("fakeSub").build(); + public static KumiteUser user() { + KumiteUserRawRaw rawRaw = KumiteUserRawRaw.builder().providerId("kumite").sub("fakeSub").build(); KumiteUserRaw raw = KumiteUserRaw.builder() .rawRaw(rawRaw) .username("fakeUsername") .email("fake@fake") .name("Fake User") .build(); - return KumiteUser.builder().accountId(FAKE_ACCOUNT_ID).playerId(FAKE_PLAYER_ID1).raw(raw).build(); + return KumiteUser.builder().accountId(ACCOUNT_ID).playerId(PLAYER_ID1).raw(raw).build(); } public static KumitePlayer fakePlayer() { - return KumitePlayer.builder().playerId(FAKE_PLAYER_ID1).accountId(FAKE_ACCOUNT_ID).build(); + return KumitePlayer.builder().playerId(PLAYER_ID1).accountId(ACCOUNT_ID).build(); } public static KumitePlayer fakePlayer(int i) { - return KumitePlayer.builder().playerId(fakePlayerId(i)).accountId(FAKE_ACCOUNT_ID).build(); + return KumitePlayer.builder().playerId(fakePlayerId(i)).accountId(ACCOUNT_ID).build(); } public static Set fakePlayers() { - return Set.of(FakePlayerTokens.FAKE_PLAYER_ID1, FakePlayerTokens.FAKE_PLAYER_ID2); + return Set.of(FakePlayer.PLAYER_ID1, FakePlayer.PLAYER_ID2); } } diff --git a/public/src/main/java/eu/solven/kumite/account/fake_player/RandomPlayer.java b/public/src/main/java/eu/solven/kumite/account/fake_player/RandomPlayer.java new file mode 100644 index 0000000..954e554 --- /dev/null +++ b/public/src/main/java/eu/solven/kumite/account/fake_player/RandomPlayer.java @@ -0,0 +1,67 @@ +package eu.solven.kumite.account.fake_player; + +import java.util.Set; +import java.util.UUID; + +import eu.solven.kumite.account.KumiteUser; +import eu.solven.kumite.account.KumiteUserRaw; +import eu.solven.kumite.account.KumiteUserRawRaw; +import eu.solven.kumite.player.KumitePlayer; +import lombok.extern.slf4j.Slf4j; + +/** + * Various tools specific to the RandomPlayer. This player is useful to generate activity even for a PRD contest-server, + * circumventing the need for an actual login flow, with an external login provider. + * + * @author Benoit Lacelle + * + */ +@Slf4j +public class RandomPlayer { + + public static final UUID ACCOUNT_ID = UUID.fromString("FFFFFFFF-FFFF-FFFF-FFFF-000000000000"); + public static final UUID PLAYERID_1 = UUID.fromString("FFFFFFFF-FFFF-FFFF-FFFF-111111111111"); + public static final UUID RANDOM_PLAYERID2 = UUID.fromString("FFFFFFFF-FFFF-FFFF-FFFF-222222222222"); + + public static UUID randomPlayerId(int playerIndex) { + if (playerIndex == 0) { + return PLAYERID_1; + } else if (playerIndex == 1) { + return RANDOM_PLAYERID2; + } else { + throw new IllegalArgumentException("There is no randomPlayer for playerIndex=" + playerIndex); + } + } + + public static boolean isRandomPlayer(UUID playerId) { + if (PLAYERID_1.equals(playerId) || RANDOM_PLAYERID2.equals(playerId)) { + return true; + } else { + return false; + } + } + + public static KumiteUser user() { + KumiteUserRawRaw rawRaw = KumiteUserRawRaw.builder().providerId("kumite").sub("randomSub").build(); + KumiteUserRaw raw = KumiteUserRaw.builder() + .rawRaw(rawRaw) + .username("randomUsername") + .email("random@random") + .name("Random User") + .build(); + return KumiteUser.builder().accountId(ACCOUNT_ID).playerId(PLAYERID_1).raw(raw).build(); + } + + public static KumitePlayer randomPlayer() { + return KumitePlayer.builder().playerId(PLAYERID_1).accountId(ACCOUNT_ID).build(); + } + + public static KumitePlayer randomPlayer(int i) { + return KumitePlayer.builder().playerId(randomPlayerId(i)).accountId(ACCOUNT_ID).build(); + } + + public static Set randomPlayers() { + return Set.of(RandomPlayer.PLAYERID_1, RandomPlayer.RANDOM_PLAYERID2); + } + +} diff --git a/redis/src/main/java/eu/solven/kumite/contest/persistence/RedisContestRepository.java b/redis/src/main/java/eu/solven/kumite/contest/persistence/RedisContestRepository.java index 64a33a7..b51d452 100644 --- a/redis/src/main/java/eu/solven/kumite/contest/persistence/RedisContestRepository.java +++ b/redis/src/main/java/eu/solven/kumite/contest/persistence/RedisContestRepository.java @@ -77,4 +77,9 @@ public Stream> stream() { .map(contestId -> Map.entry(contestId, getById(contestId).orElse(ContestCreationMetadata.empty()))) .filter(e -> !e.getValue().getGameId().equals(IGameMetadataConstants.EMPTY)); } + + @Override + public void archive(UUID contestId) { + // Do not remove, as TTL will do its job in due-time + } } diff --git a/redis/src/main/java/eu/solven/kumite/user/RedisUserRepository.java b/redis/src/main/java/eu/solven/kumite/user/RedisUserRepository.java index f0dcf52..a3914d7 100644 --- a/redis/src/main/java/eu/solven/kumite/user/RedisUserRepository.java +++ b/redis/src/main/java/eu/solven/kumite/user/RedisUserRepository.java @@ -8,10 +8,10 @@ import eu.solven.kumite.account.IKumiteUserRawRawRepository; import eu.solven.kumite.account.IKumiteUserRepository; +import eu.solven.kumite.account.InMemoryUserRepository; import eu.solven.kumite.account.KumiteUser; import eu.solven.kumite.account.KumiteUserRaw; import eu.solven.kumite.account.KumiteUserRawRaw; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; import eu.solven.kumite.player.IAccountPlayersRegistry; import eu.solven.kumite.player.KumitePlayer; import eu.solven.kumite.redis.RepositoryKey; @@ -103,11 +103,8 @@ public KumiteUser registerOrUpdate(KumiteUserRaw kumiteUserRaw) { return getUser(rawRaw).orElseThrow(() -> new IllegalStateException("No user through we just registered one")); } - private UUID generateAccountId(KumiteUserRawRaw rawRaw) { - if (rawRaw.equals(FakePlayerTokens.fakeUser().getRaw().getRawRaw())) { - return FakePlayerTokens.FAKE_ACCOUNT_ID; - } - return uuidGenerator.randomUUID(); + protected UUID generateAccountId(KumiteUserRawRaw rawRaw) { + return InMemoryUserRepository.generateAccountId(uuidGenerator, rawRaw); } } diff --git a/server/pom.xml b/server/pom.xml index 66e01ed..bc7caa6 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -39,11 +39,6 @@ spring-boot-starter-oauth2-client
- - org.springframework.boot - spring-boot-starter-oauth2-resource-server - - org.springframework.boot diff --git a/server/src/main/java/eu/solven/kumite/app/InjectKumiteAccountsConfig.java b/server/src/main/java/eu/solven/kumite/app/InjectKumiteAccountsConfig.java new file mode 100644 index 0000000..0917bd6 --- /dev/null +++ b/server/src/main/java/eu/solven/kumite/app/InjectKumiteAccountsConfig.java @@ -0,0 +1,37 @@ +package eu.solven.kumite.app; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +import eu.solven.kumite.account.KumiteUsersRegistry; +import eu.solven.kumite.account.fake_player.FakePlayer; +import eu.solven.kumite.account.fake_player.RandomPlayer; +import eu.solven.kumite.player.IAccountPlayersRegistry; +import lombok.extern.slf4j.Slf4j; + +@Configuration +@Slf4j +public class InjectKumiteAccountsConfig { + + @Profile(IKumiteSpringProfiles.P_FAKEUSER) + @Bean + public Void initFakePlayer(KumiteUsersRegistry usersRegistry, IAccountPlayersRegistry accountPlayersRegistry) { + log.info("Registering the {} account and players", IKumiteSpringProfiles.P_FAKEUSER); + + usersRegistry.registerOrUpdate(FakePlayer.user().getRaw()); + accountPlayersRegistry.registerPlayer(FakePlayer.fakePlayer(1)); + + return null; + } + + @Bean + public Void initRandomPlayer(KumiteUsersRegistry usersRegistry, IAccountPlayersRegistry accountPlayersRegistry) { + log.info("Registering the random account and players"); + + usersRegistry.registerOrUpdate(RandomPlayer.user().getRaw()); + accountPlayersRegistry.registerPlayer(RandomPlayer.randomPlayer(1)); + + return null; + } +} diff --git a/server/src/main/java/eu/solven/kumite/app/KumiteServerComponentsConfiguration.java b/server/src/main/java/eu/solven/kumite/app/KumiteServerComponentsConfiguration.java index 6e49a3c..3fa106b 100644 --- a/server/src/main/java/eu/solven/kumite/app/KumiteServerComponentsConfiguration.java +++ b/server/src/main/java/eu/solven/kumite/app/KumiteServerComponentsConfiguration.java @@ -6,10 +6,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Profile; import eu.solven.kumite.account.KumiteUsersRegistry; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; import eu.solven.kumite.app.persistence.InMemoryKumiteConfiguration; import eu.solven.kumite.app.persistence.RedisKumiteConfiguration; import eu.solven.kumite.board.BoardHandler; @@ -45,6 +43,7 @@ PlayerMovesHandler.class, InjectDefaultGamesConfig.class, + InjectKumiteAccountsConfig.class, ContendersFromBoard.class, @@ -63,14 +62,4 @@ public BoardLifecycleManager boardLifecycleManager(BoardsRegistry boardRegistry, return new BoardLifecycleManager(boardRegistry, contestPlayersRegistry, boardEvolutionExecutor); } - - @Profile(IKumiteSpringProfiles.P_FAKEUSER) - @Bean - public Void initFakePlayer(KumiteUsersRegistry usersRegistry) { - log.info("Registering the {} account and players", IKumiteSpringProfiles.P_FAKEUSER); - - usersRegistry.registerOrUpdate(FakePlayerTokens.fakeUser().getRaw()); - - return null; - } } diff --git a/server/src/main/java/eu/solven/kumite/app/webflux/api/KumiteApiRouter.java b/server/src/main/java/eu/solven/kumite/app/webflux/api/KumiteApiRouter.java index 5d367b4..90b5d6e 100644 --- a/server/src/main/java/eu/solven/kumite/app/webflux/api/KumiteApiRouter.java +++ b/server/src/main/java/eu/solven/kumite/app/webflux/api/KumiteApiRouter.java @@ -77,9 +77,10 @@ public RouterFunction apiRoutes(PlayerVerifierFilterFunction pla .GET(json("/contests"), contestSearchHandler::listContests, ops -> ops.operationId("searchContest").parameter(gameId)) - .POST(json("/contests"), - contestSearchHandler::generateContest, - ops -> ops.operationId("publishContest")) + .POST(json("/contests"), contestSearchHandler::openContest, ops -> ops.operationId("publishContest")) + .DELETE(json("/contests"), + contestSearchHandler::openContest, + ops -> ops.operationId("deleteContest").parameter(contestId)) .GET(json("/board"), boardHandler::getBoard, @@ -114,8 +115,6 @@ public RouterFunction apiRoutes(PlayerVerifierFilterFunction pla // webhooksHandler::dropWebhooks, // ops -> ops.operationId("deleteWebhook")) - .filter(playerVerifierFilterFunction, ops -> { - }) .build(); } diff --git a/server/src/main/java/eu/solven/kumite/app/webflux/api/KumiteLoginController.java b/server/src/main/java/eu/solven/kumite/app/webflux/api/KumiteLoginController.java index dda35d6..e6e418e 100644 --- a/server/src/main/java/eu/solven/kumite/app/webflux/api/KumiteLoginController.java +++ b/server/src/main/java/eu/solven/kumite/app/webflux/api/KumiteLoginController.java @@ -15,7 +15,11 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -30,7 +34,6 @@ import eu.solven.kumite.account.KumiteUser; import eu.solven.kumite.account.KumiteUserRawRaw; import eu.solven.kumite.account.KumiteUsersRegistry; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; import eu.solven.kumite.account.login.IKumiteTestConstants; import eu.solven.kumite.app.IKumiteSpringProfiles; import eu.solven.kumite.oauth2.authorizationserver.KumiteTokenService; @@ -38,6 +41,7 @@ import eu.solven.kumite.player.KumitePlayer; import eu.solven.kumite.security.LoginRouteButNotAuthenticatedException; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Mono; /** @@ -49,6 +53,7 @@ @RestController @RequestMapping("/api/login/v1") @AllArgsConstructor +@Slf4j public class KumiteLoginController { public static final String PROVIDERID_GITHUB = "github"; @Deprecated @@ -88,12 +93,12 @@ public class KumiteLoginController { @GetMapping("/html") public ResponseEntity loginpage(@AuthenticationPrincipal OAuth2User oauth2User) { String url; - if (env.acceptsProfiles(Profiles.of(IKumiteSpringProfiles.P_FAKEUSER))) { + if (oauth2User != null) { + url = "login?success"; + } else if (env.acceptsProfiles(Profiles.of(IKumiteSpringProfiles.P_FAKEUSER))) { url = "login?" + IKumiteSpringProfiles.P_FAKEUSER; - } else if (oauth2User == null) { - url = "login"; } else { - url = "login?success"; + url = "login"; } // Spring-OAuth2-Login returns FOUND in case current user is not authenticated: let's follow this choice is=n @@ -103,19 +108,26 @@ public ResponseEntity loginpage(@AuthenticationPrincipal OAuth2User oauth2Use // @PreAuthorize("isAuthenticated()") @GetMapping("/user") - public Mono user(@AuthenticationPrincipal Mono oauth2User) { - if (env.acceptsProfiles(Profiles.of(IKumiteSpringProfiles.P_FAKEUSER))) { - return Mono.just(usersRegistry.getUser(FakePlayerTokens.FAKE_ACCOUNT_ID)); - } else if (oauth2User == null) { - // Happens if this route is called without authentication - return Mono.error(() -> new LoginRouteButNotAuthenticatedException("Lack of OAuth2 user")); - } else { - return oauth2User.map(o -> { - KumiteUser user = userFromOAuth2(o); + public Mono user() { + return ReactiveSecurityContextHolder.getContext().map(sc -> { + Authentication authentication = sc.getAuthentication(); + + if (authentication instanceof UsernamePasswordAuthenticationToken usernameToken) { + // This happens on BASIC auth (for fakePlayer) + return userFromUsername(usernameToken); + } else if (authentication instanceof OAuth2AuthenticationToken oauth2Token) { + // This happens on OAuth2 auth (e.g. Github login) + return userFromOAuth2(oauth2Token.getPrincipal()); + } else { + throw new LoginRouteButNotAuthenticatedException("Lack of authentication"); + } + }).switchIfEmpty(Mono.error(() -> new LoginRouteButNotAuthenticatedException("No user"))); + } - return user; - }).switchIfEmpty(Mono.error(() -> new LoginRouteButNotAuthenticatedException("No user"))); - } + private KumiteUser userFromUsername(UsernamePasswordAuthenticationToken usernameToken) { + UUID accountId = UUID.fromString(usernameToken.getName()); + KumiteUser user = usersRegistry.getUser(accountId); + return user; } private KumiteUser userFromOAuth2(OAuth2User o) { @@ -152,10 +164,9 @@ private String guessProviderId(OAuth2User o) { } @GetMapping("/oauth2/token") - public Mono token(@AuthenticationPrincipal Mono oauth2User, - @RequestParam(name = "player_id", required = false) String rawPlayerId, + public Mono token(@RequestParam(name = "player_id", required = false) String rawPlayerId, @RequestParam(name = "refresh_token", defaultValue = "false") boolean requestRefreshToken) { - return user(oauth2User).map(user -> { + return user().map(user -> { if (requestRefreshToken) { // TODO Restrict if `rawPlayerId` is provided. if (!StringUtils.isEmpty(rawPlayerId)) { @@ -164,11 +175,13 @@ public Mono token(@AuthenticationPrincipal Mono oauth2User, List players = playersRegistry.makeDynamicHasPlayers(user.getAccountId()).getPlayers(); // Beware this would not allow playerIds generated after the refresh_token creation Set playerIds = players.stream().map(KumitePlayer::getPlayerId).collect(Collectors.toSet()); + log.info("Generating an refresh_token for accountId={} playerIds={}", user.getAccountId(), playerIds); return kumiteTokenService.wrapInJwtRefreshToken(user, playerIds); } else { UUID playerId = KumiteHandlerHelper.optUuid(Optional.ofNullable(rawPlayerId)).orElse(user.getPlayerId()); checkValidPlayerId(user, playerId); + log.info("Generating an access_token for accountId={} playerId={}", user.getAccountId(), playerId); return kumiteTokenService.wrapInJwtAccessToken(user, playerId); } }); diff --git a/server/src/main/java/eu/solven/kumite/security/KumiteSecurity.java b/server/src/main/java/eu/solven/kumite/security/KumiteSecurity.java index 17d1d9a..375d272 100644 --- a/server/src/main/java/eu/solven/kumite/security/KumiteSecurity.java +++ b/server/src/main/java/eu/solven/kumite/security/KumiteSecurity.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.WebExceptionHandler; +import eu.solven.kumite.account.JwtUserContextHolder; import eu.solven.kumite.app.IKumiteSpringProfiles; import eu.solven.kumite.app.webflux.KumiteWebExceptionHandler; import eu.solven.kumite.app.webflux.api.KumiteLoginController; @@ -30,6 +31,8 @@ KumiteResourceServerConfiguration.class, KumiteTokenService.class, + JwtUserContextHolder.class, + }) @Slf4j public class KumiteSecurity { @@ -68,10 +71,10 @@ public Void checkSpringProfilesConsistency(Environment env) { return null; } -// @Bean -// WebFilter kumiteExceptionRoutingWebFilter() { -// return new KumiteExceptionRoutingWebFilter(); -// } + // @Bean + // WebFilter kumiteExceptionRoutingWebFilter() { + // return new KumiteExceptionRoutingWebFilter(); + // } @Bean WebExceptionHandler kumiteWebExceptionHandler() { diff --git a/server/src/main/java/eu/solven/kumite/security/SocialWebFluxSecurity.java b/server/src/main/java/eu/solven/kumite/security/SocialWebFluxSecurity.java index f0d10e7..aa1d5c6 100644 --- a/server/src/main/java/eu/solven/kumite/security/SocialWebFluxSecurity.java +++ b/server/src/main/java/eu/solven/kumite/security/SocialWebFluxSecurity.java @@ -1,6 +1,8 @@ package eu.solven.kumite.security; import java.net.URI; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Bean; @@ -10,19 +12,25 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; import org.springframework.security.authentication.ReactiveAuthenticationManager; +import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler; +import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository; import org.springframework.security.web.server.csrf.WebSessionServerCsrfTokenRepository; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; import com.nimbusds.jwt.JWT; +import eu.solven.kumite.account.fake_player.FakePlayer; import eu.solven.kumite.app.IKumiteSpringProfiles; import eu.solven.kumite.oauth2.resourceserver.KumiteResourceServerConfiguration; import eu.solven.kumite.security.oauth2.KumiteOAuth2UserService; @@ -47,11 +55,13 @@ public class SocialWebFluxSecurity { public SecurityWebFilterChain configureUi(ServerProperties serverProperties, ServerHttpSecurity http, Environment env) { - boolean isSsl = serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled(); - ReactiveAuthenticationManager ram = auth -> { - throw new IllegalStateException(); - }; + boolean isFakeUser = env.acceptsProfiles(Profiles.of(IKumiteSpringProfiles.P_FAKEUSER)); + if (isFakeUser) { + log.warn("{}=true", IKumiteSpringProfiles.P_FAKEUSER); + } else { + log.info("{}=false", IKumiteSpringProfiles.P_FAKEUSER); + } return http // We restrict the scope of this UI securityFilterChain to UI routes @@ -123,7 +133,11 @@ public SecurityWebFilterChain configureUi(ServerProperties serverProperties, // `/html/login` has to be synced with the SPA login route .formLogin(login -> { - String loginPage = "/html/login".formatted(isSsl ? "s" : ""); + ReactiveAuthenticationManager ram = auth -> { + throw new IllegalStateException(); + }; + + String loginPage = "/html/login"; login.loginPage(loginPage) // Required not to get an NPE at `.build()` .authenticationManager(ram); @@ -132,10 +146,34 @@ public SecurityWebFilterChain configureUi(ServerProperties serverProperties, // https://docs.spring.io/spring-security/reference/servlet/oauth2/client/authorization-grants.html // https://stackoverflow.com/questions/74242738/how-to-logout-from-oauth-signed-in-web-app-with-github .oauth2Login(oauth2 -> { - String loginSuccess = "/html/login?success".formatted(isSsl ? "s" : ""); + String loginSuccess = "/html/login?success"; oauth2.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler(loginSuccess)); }) + .httpBasic(basic -> { + Map userDetails = new ConcurrentHashMap<>(); + + UserDetails fakeUser = User.builder() + .username(FakePlayer.ACCOUNT_ID.toString()) + // `{noop}` relates with `PasswordEncoderFactories.createDelegatingPasswordEncoder()` + .password("{noop}" + "no_password") + .roles(IKumiteSpringProfiles.P_FAKEUSER) + .build(); + + userDetails.put(fakeUser.getUsername(), fakeUser); + + UserDetailsRepositoryReactiveAuthenticationManager ram = + new UserDetailsRepositoryReactiveAuthenticationManager( + new MapReactiveUserDetailsService(userDetails)); + + if (isFakeUser) { + basic.authenticationManager(ram) + .securityContextRepository(new WebSessionServerSecurityContextRepository()); + } else { + basic.disable(); + } + }) + .logout(logout -> { RedirectServerLogoutSuccessHandler logoutSuccessHandler = new RedirectServerLogoutSuccessHandler(); // We need to redirect to a 2XX URL, and not a 3XX URL, as Fetch API can not intercept redirections. @@ -161,8 +199,8 @@ public SecurityWebFilterChain configureApi(ServerHttpSecurity http, Environment env, ReactiveJwtDecoder jwtDecoder) { - boolean fakeUser = env.acceptsProfiles(Profiles.of(IKumiteSpringProfiles.P_FAKEUSER)); - if (fakeUser) { + boolean isFakeUser = env.acceptsProfiles(Profiles.of(IKumiteSpringProfiles.P_FAKEUSER)); + if (isFakeUser) { log.warn("{}=true", IKumiteSpringProfiles.P_FAKEUSER); } else { log.info("{}=false", IKumiteSpringProfiles.P_FAKEUSER); @@ -195,7 +233,7 @@ public SecurityWebFilterChain configureApi(ServerHttpSecurity http, .permitAll() // If fakeUser==true, we allow the reset route (for integration tests) - .pathMatchers(fakeUser ? "/api/v1/clear" : "nonono") + .pathMatchers(isFakeUser ? "/api/v1/clear" : "nonono") .permitAll() // The rest needs to be authenticated diff --git a/server/src/test/java/eu/solven/kumite/account/fake_player/RunFakePlayerToken.java b/server/src/test/java/eu/solven/kumite/account/fake_player/RunFakePlayerToken.java index 21fe6bf..bb4f796 100644 --- a/server/src/test/java/eu/solven/kumite/account/fake_player/RunFakePlayerToken.java +++ b/server/src/test/java/eu/solven/kumite/account/fake_player/RunFakePlayerToken.java @@ -48,8 +48,8 @@ public static void main(String[] args) { @Bean public Void generateFakePlayerToken(KumiteTokenService tokenService) { - String accessToken = tokenService.generateAccessToken(FakePlayerTokens.fakeUser(), - Set.of(FakePlayerTokens.FAKE_PLAYER_ID1, FakePlayerTokens.FAKE_PLAYER_ID2), + String accessToken = tokenService.generateAccessToken(FakePlayer.user(), + Set.of(FakePlayer.PLAYER_ID1, FakePlayer.PLAYER_ID2), Duration.ofDays(365), false); diff --git a/server/src/test/java/eu/solven/kumite/app/it/TestKumiteApiRouter.java b/server/src/test/java/eu/solven/kumite/app/it/TestKumiteApiRouter.java index dddc5d6..0d0df3b 100644 --- a/server/src/test/java/eu/solven/kumite/app/it/TestKumiteApiRouter.java +++ b/server/src/test/java/eu/solven/kumite/app/it/TestKumiteApiRouter.java @@ -14,7 +14,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.FakePlayer; import eu.solven.kumite.app.IKumiteSpringProfiles; import eu.solven.kumite.app.KumiteContestServerApplication; import eu.solven.kumite.app.webflux.KumiteWebExceptionHandler; @@ -46,8 +46,8 @@ public class TestKumiteApiRouter { KumiteTokenService tokenService; protected String generateAccessToken() { - return tokenService.generateAccessToken(FakePlayerTokens.fakeUser(), - Set.of(FakePlayerTokens.FAKE_PLAYER_ID1), + return tokenService.generateAccessToken(FakePlayer.user(), + Set.of(FakePlayer.PLAYER_ID1), Duration.ofMinutes(1), false); } diff --git a/server/src/test/java/eu/solven/kumite/app/it/TestKumiteRouterSpringConfig.java b/server/src/test/java/eu/solven/kumite/app/it/TestKumiteRouterSpringConfig.java index 6cadb3d..5365589 100644 --- a/server/src/test/java/eu/solven/kumite/app/it/TestKumiteRouterSpringConfig.java +++ b/server/src/test/java/eu/solven/kumite/app/it/TestKumiteRouterSpringConfig.java @@ -5,13 +5,17 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit.jupiter.SpringExtension; +import eu.solven.kumite.account.JwtUserContextHolder; import eu.solven.kumite.app.KumiteServerComponentsConfiguration; import eu.solven.kumite.app.webflux.KumiteWebFluxConfiguration; import eu.solven.kumite.app.webflux.api.GreetingHandler; import lombok.extern.slf4j.Slf4j; @ExtendWith(SpringExtension.class) -@SpringBootTest(classes = { KumiteWebFluxConfiguration.class, KumiteServerComponentsConfiguration.class, }, +@SpringBootTest( + classes = { KumiteWebFluxConfiguration.class, + KumiteServerComponentsConfiguration.class, + JwtUserContextHolder.class, }, webEnvironment = SpringBootTest.WebEnvironment.MOCK) @Slf4j public class TestKumiteRouterSpringConfig { diff --git a/server/src/test/java/eu/solven/kumite/app/it/security/KumiteServerSecurityApplication.java b/server/src/test/java/eu/solven/kumite/app/it/security/KumiteServerSecurityApplication.java index 93acca8..406c18d 100644 --- a/server/src/test/java/eu/solven/kumite/app/it/security/KumiteServerSecurityApplication.java +++ b/server/src/test/java/eu/solven/kumite/app/it/security/KumiteServerSecurityApplication.java @@ -11,6 +11,7 @@ import eu.solven.kumite.account.InMemoryUserRepository; import eu.solven.kumite.account.KumiteUsersRegistry; +import eu.solven.kumite.app.InjectKumiteAccountsConfig; import eu.solven.kumite.app.webflux.PlayerVerifierFilterFunction; import eu.solven.kumite.app.webflux.api.AccessTokenHandler; import eu.solven.kumite.app.webflux.api.GreetingHandler; @@ -40,6 +41,8 @@ // IAccountPlayersRegistry is needed as security often checks the players of an account BijectiveAccountPlayersRegistry.class, + InjectKumiteAccountsConfig.class, + }) public class KumiteServerSecurityApplication { diff --git a/server/src/test/java/eu/solven/kumite/app/it/security/TestSecurity_WithFakeUser.java b/server/src/test/java/eu/solven/kumite/app/it/security/TestSecurity_WithFakeUser.java new file mode 100644 index 0000000..eebc7c4 --- /dev/null +++ b/server/src/test/java/eu/solven/kumite/app/it/security/TestSecurity_WithFakeUser.java @@ -0,0 +1,80 @@ +package eu.solven.kumite.app.it.security; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import eu.solven.kumite.account.fake_player.FakePlayer; +import eu.solven.kumite.app.IKumiteSpringProfiles; +import eu.solven.kumite.app.KumiteJackson; +import eu.solven.kumite.app.webflux.api.KumiteLoginController; +import eu.solven.kumite.login.AccessTokenWrapper; +import lombok.extern.slf4j.Slf4j; + +/** + * OAuth2 enables logging-in a subset of APIs, especially the login APIs. + * + * @author Benoit Lacelle + * + */ +@ExtendWith(SpringExtension.class) +@ActiveProfiles({ IKumiteSpringProfiles.P_UNSAFE, IKumiteSpringProfiles.P_FAKEUSER }) +@Slf4j +public class TestSecurity_WithFakeUser extends TestSecurity_WithOAuth2User { + + @Test + @Override + public void testLoginAccessToken() { + log.debug("About {}", KumiteLoginController.class); + + webTestClient + + .get() + .uri("/api/login/v1/oauth2/token") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders.encodeBasicAuth(FakePlayer.ACCOUNT_ID.toString(), + "no_password", + StandardCharsets.UTF_8)) + .exchange() + + .expectStatus() + .isOk() + .expectBody(AccessTokenWrapper.class) + .value(token -> { + Map asMap = KumiteJackson.objectMapper().convertValue(token, Map.class); + + Assertions.assertThat(asMap) + .containsKey("access_token") + .containsEntry("token_type", "Bearer") + .containsEntry("player_id", FakePlayer.PLAYER_ID1.toString()) + .containsEntry("expires_in", 3600L) + .hasSize(4); + }); + } + + @Test + public void testLoginAccessToken_invalidUser() { + log.debug("About {}", KumiteLoginController.class); + + webTestClient + + .get() + .uri("/api/login/v1/oauth2/token") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, + "Basic " + HttpHeaders + .encodeBasicAuth("someUnknownUser", "no_password", StandardCharsets.UTF_8)) + .exchange() + + .expectStatus() + .isUnauthorized(); + } +} \ No newline at end of file diff --git a/server/src/test/java/eu/solven/kumite/app/it/security/TestSecurity_WithJwtUser.java b/server/src/test/java/eu/solven/kumite/app/it/security/TestSecurity_WithJwtUser.java index 007b8e6..9f71c33 100644 --- a/server/src/test/java/eu/solven/kumite/app/it/security/TestSecurity_WithJwtUser.java +++ b/server/src/test/java/eu/solven/kumite/app/it/security/TestSecurity_WithJwtUser.java @@ -21,7 +21,7 @@ import org.springframework.test.web.reactive.server.StatusAssertions; import org.springframework.test.web.reactive.server.WebTestClient; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.RandomPlayer; import eu.solven.kumite.app.IKumiteSpringProfiles; import eu.solven.kumite.app.webflux.KumiteWebExceptionHandler; import eu.solven.kumite.app.webflux.api.AccessTokenHandler; @@ -56,15 +56,15 @@ public class TestSecurity_WithJwtUser { KumiteTokenService tokenService; protected String generateAccessToken() { - return tokenService.generateAccessToken(FakePlayerTokens.fakeUser(), - Set.of(FakePlayerTokens.FAKE_PLAYER_ID1), + return tokenService.generateAccessToken(RandomPlayer.user(), + Set.of(RandomPlayer.PLAYERID_1), Duration.ofMinutes(1), false); } protected String generateRefreshToken() { - return tokenService.generateAccessToken(FakePlayerTokens.fakeUser(), - Set.of(FakePlayerTokens.FAKE_PLAYER_ID1), + return tokenService.generateAccessToken(RandomPlayer.user(), + Set.of(RandomPlayer.PLAYERID_1), Duration.ofMinutes(1), true); } @@ -274,7 +274,7 @@ public void testMakeRefreshToken() { // We need an oauth2 user, not a jwt user expectStatus.isUnauthorized().expectBody(Map.class).value(bodyAsMap -> { - Assertions.assertThat(bodyAsMap).containsEntry("error_message", "Lack of OAuth2 user").hasSize(1); + Assertions.assertThat(bodyAsMap).containsEntry("error_message", "No user").hasSize(1); }); } @@ -285,7 +285,7 @@ public void testRefreshTokenToAccessToken() { log.debug("About {}", AccessTokenHandler.class); StatusAssertions expectStatus = webTestClient.get() - .uri("/api/v1/oauth2/token?player_id=11111111-1111-1111-1111-111111111111") + .uri("/api/v1/oauth2/token?player_id=" + RandomPlayer.PLAYERID_1) .header(HttpHeaders.AUTHORIZATION, "Bearer " + generateRefreshToken()) .accept(MediaType.APPLICATION_JSON) .exchange() diff --git a/server/src/test/java/eu/solven/kumite/app/it/security/TestSecurity_WithOAuth2User.java b/server/src/test/java/eu/solven/kumite/app/it/security/TestSecurity_WithOAuth2User.java index 8853636..1040a36 100644 --- a/server/src/test/java/eu/solven/kumite/app/it/security/TestSecurity_WithOAuth2User.java +++ b/server/src/test/java/eu/solven/kumite/app/it/security/TestSecurity_WithOAuth2User.java @@ -49,14 +49,15 @@ @ActiveProfiles({ IKumiteSpringProfiles.P_UNSAFE, }) @Slf4j // https://stackoverflow.com/questions/73881370/mocking-oauth2-client-with-webtestclient-for-servlet-applications-results-in-nul -@AutoConfigureWebTestClient +// https://stackoverflow.com/questions/56784289/autoconfigurewebtestclienttimeout-600000-has-no-effect +@AutoConfigureWebTestClient(timeout = "PT10M") @WithMockUser public class TestSecurity_WithOAuth2User { // Spring Boot will create a `WebTestClient` for you, // already configure and ready to issue requests against "localhost:RANDOM_PORT" @Autowired - private WebTestClient webTestClient; + WebTestClient webTestClient; @Autowired KumiteOAuth2UserService oauth2UserService; diff --git a/server/src/test/java/eu/solven/kumite/app/webflux/api/TestKumiteLoginController.java b/server/src/test/java/eu/solven/kumite/app/webflux/api/TestKumiteLoginController.java index ca2b954..08fb6dc 100644 --- a/server/src/test/java/eu/solven/kumite/app/webflux/api/TestKumiteLoginController.java +++ b/server/src/test/java/eu/solven/kumite/app/webflux/api/TestKumiteLoginController.java @@ -10,7 +10,7 @@ import eu.solven.kumite.account.InMemoryUserRepository; import eu.solven.kumite.account.KumiteUsersRegistry; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.FakePlayer; import eu.solven.kumite.oauth2.IKumiteOAuth2Constants; import eu.solven.kumite.oauth2.authorizationserver.KumiteTokenService; import eu.solven.kumite.player.IAccountPlayersRegistry; @@ -54,7 +54,7 @@ public class TestKumiteLoginController { public void testPlayer_invalid() { Assertions .assertThatThrownBy( - () -> controller.checkValidPlayerId(FakePlayerTokens.fakeUser(), uuidGenerator.randomUUID())) + () -> controller.checkValidPlayerId(FakePlayer.user(), uuidGenerator.randomUUID())) .isInstanceOf(IllegalArgumentException.class); } } diff --git a/server/src/test/java/eu/solven/kumite/user/TestInMemoryUserRepository.java b/server/src/test/java/eu/solven/kumite/user/TestInMemoryUserRepository.java index 4702673..4163789 100644 --- a/server/src/test/java/eu/solven/kumite/user/TestInMemoryUserRepository.java +++ b/server/src/test/java/eu/solven/kumite/user/TestInMemoryUserRepository.java @@ -6,20 +6,53 @@ import org.junit.jupiter.api.Test; import eu.solven.kumite.account.InMemoryUserRepository; +import eu.solven.kumite.account.KumiteUser; import eu.solven.kumite.account.KumiteUserRawRaw; -import eu.solven.kumite.account.fake_player.FakePlayerTokens; +import eu.solven.kumite.account.fake_player.FakePlayer; +import eu.solven.kumite.account.fake_player.RandomPlayer; +import eu.solven.kumite.account.login.IKumiteTestConstants; import eu.solven.kumite.player.persistence.BijectiveAccountPlayersRegistry; +import eu.solven.kumite.tools.JdkUuidGenerator; public class TestInMemoryUserRepository { BijectiveAccountPlayersRegistry playersRegistry = new BijectiveAccountPlayersRegistry(); + InMemoryUserRepository userRepository = new InMemoryUserRepository(JdkUuidGenerator.INSTANCE, playersRegistry); @Test - public void testFakeAccount() { - InMemoryUserRepository userRepository = InMemoryUserRepository.forTests(playersRegistry); + public void testRegisterUser() { + KumiteUser user = userRepository.registerOrUpdate(IKumiteTestConstants.userRaw()); - Optional optRawRaw = userRepository.getUser(FakePlayerTokens.FAKE_ACCOUNT_ID); + Optional optRawRaw = userRepository.getUser(user.getAccountId()); + Assertions.assertThat(optRawRaw).isPresent().contains(user.getRaw().getRawRaw()); + } - Assertions.assertThat(optRawRaw).isPresent(); + @Test + public void testFakeUser() { + // Not present by default + { + Optional optRawRaw = userRepository.getUser(FakePlayer.ACCOUNT_ID); + Assertions.assertThat(optRawRaw).isEmpty(); + } + + KumiteUser user = userRepository.registerOrUpdate(FakePlayer.user().getRaw()); + Assertions.assertThat(user.getAccountId()).isEqualTo(FakePlayer.ACCOUNT_ID); + + Optional optRawRaw = userRepository.getUser(user.getAccountId()); + Assertions.assertThat(optRawRaw).isPresent().contains(user.getRaw().getRawRaw()); + } + @Test + public void testRandomUser() { + // Not present by default + { + Optional optRawRaw = userRepository.getUser(RandomPlayer.ACCOUNT_ID); + Assertions.assertThat(optRawRaw).isEmpty(); + } + + KumiteUser user = userRepository.registerOrUpdate(RandomPlayer.user().getRaw()); + Assertions.assertThat(user.getAccountId()).isEqualTo(RandomPlayer.ACCOUNT_ID); + + Optional optRawRaw = userRepository.getUser(user.getAccountId()); + Assertions.assertThat(optRawRaw).isPresent().contains(user.getRaw().getRawRaw()); } } diff --git a/tools/pom.xml b/tools/pom.xml index 4f3bd07..f9532bb 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -19,6 +19,12 @@ 5.1.0 + + + io.projectreactor + reactor-core + + org.springframework diff --git a/tools/src/main/java/eu/solven/kumite/account/IKumiteUserContextHolder.java b/tools/src/main/java/eu/solven/kumite/account/IKumiteUserContextHolder.java new file mode 100644 index 0000000..bb1b051 --- /dev/null +++ b/tools/src/main/java/eu/solven/kumite/account/IKumiteUserContextHolder.java @@ -0,0 +1,15 @@ +package eu.solven.kumite.account; + +import java.util.UUID; + +import reactor.core.publisher.Mono; + +/** + * Give access to the authenticated user. + * + * @author Benoit Lacelle + * + */ +public interface IKumiteUserContextHolder { + Mono authenticatedAccountId(); +}