Skip to content

Commit 206ac81

Browse files
committed
Stabilize Snake
1 parent d46d6e6 commit 206ac81

40 files changed

+823
-458
lines changed

authorization/src/main/java/eu/solven/kumite/account/JwtUserContextHolder.java

+15
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,19 @@ public Mono<UUID> authenticatedAccountId() {
2626
});
2727
}
2828

29+
@Override
30+
public Mono<UUID> authenticatedPlayerId() {
31+
return ReactiveSecurityContextHolder.getContext().map(securityContext -> {
32+
Authentication authentication = securityContext.getAuthentication();
33+
34+
if (authentication instanceof JwtAuthenticationToken jwtAuth) {
35+
UUID accountId = UUID.fromString(jwtAuth.getToken().getClaimAsString("playerId"));
36+
37+
return accountId;
38+
} else {
39+
throw new LoginRouteButNotAuthenticatedException("Expecting a JWT token");
40+
}
41+
});
42+
}
43+
2944
}

authorization/src/main/java/eu/solven/kumite/oauth2/authorizationserver/KumiteTokenService.java

+13-5
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,6 @@ public String generateAccessToken(UUID accountId,
7979
Set<UUID> playerIds,
8080
Duration accessTokenValidity,
8181
boolean isRefreshToken) {
82-
if (!isRefreshToken && playerIds.size() != 1) {
83-
throw new IllegalArgumentException("access_token are generated for a specific single playerId");
84-
}
8582

8683
// Generating a Signed JWT
8784
// https://auth0.com/blog/rs256-vs-hs256-whats-the-difference/
@@ -105,8 +102,19 @@ public String generateAccessToken(UUID accountId,
105102
.issueTime(Date.from(now))
106103
.notBeforeTime(Date.from(now))
107104
.expirationTime(Date.from(now.plus(accessTokenValidity)))
108-
.claim("refresh_token", isRefreshToken)
109-
.claim("playerIds", playerIds);
105+
.claim("refresh_token", isRefreshToken);
106+
107+
if (isRefreshToken) {
108+
// A refreshToken may be attached to multiple playerIds
109+
claimsSetBuilder.claim("playerIds", playerIds);
110+
} else {
111+
if (playerIds.size() != 1) {
112+
throw new IllegalArgumentException("access_token are generated for a specific single playerId");
113+
}
114+
115+
// Access_token are attached to a single playerId
116+
claimsSetBuilder.claim("playerId", playerIds.iterator().next());
117+
}
110118

111119
SignedJWT signedJWT = new SignedJWT(headerBuilder.build(), claimsSetBuilder.build());
112120

authorization/src/test/java/eu/solven/kumite/oauth2/authorizationserver/TestKumiteTokenService.java

+38-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class TestKumiteTokenService {
3333
Supplier<KumiteTokenService> tokenService = () -> new KumiteTokenService(env, JdkUuidGenerator.INSTANCE);
3434

3535
@Test
36-
public void testJwt_randomSecret() throws JOSEException, ParseException {
36+
public void testJwt_refreshToken() throws JOSEException, ParseException {
3737
JWK signatureSecret = KumiteTokenService.generateSignatureSecret(JdkUuidGenerator.INSTANCE);
3838
env.setProperty(IKumiteOAuth2Constants.KEY_OAUTH2_ISSUER, "https://some.issuer.domain");
3939
env.setProperty(IKumiteOAuth2Constants.KEY_JWT_SIGNINGKEY, signatureSecret.toJSONString());
@@ -47,7 +47,7 @@ public void testJwt_randomSecret() throws JOSEException, ParseException {
4747
.details(IKumiteTestConstants.userDetails())
4848
.build();
4949
String accessToken = tokenService.get()
50-
.generateAccessToken(user.getAccountId(), Set.of(playerId), Duration.ofMinutes(1), false);
50+
.generateAccessToken(user.getAccountId(), Set.of(playerId), Duration.ofMinutes(1), true);
5151

5252
{
5353
JWSVerifier verifier = new MACVerifier((OctetSequenceKey) signatureSecret);
@@ -67,4 +67,40 @@ public void testJwt_randomSecret() throws JOSEException, ParseException {
6767
Assertions.assertThat(jwt.getAudience()).containsExactly("Kumite-Server");
6868
Assertions.assertThat(jwt.getClaimAsStringList("playerIds")).isEqualTo(List.of(playerId.toString()));
6969
}
70+
71+
@Test
72+
public void testJwt_accessToken() throws JOSEException, ParseException {
73+
JWK signatureSecret = KumiteTokenService.generateSignatureSecret(JdkUuidGenerator.INSTANCE);
74+
env.setProperty(IKumiteOAuth2Constants.KEY_OAUTH2_ISSUER, "https://some.issuer.domain");
75+
env.setProperty(IKumiteOAuth2Constants.KEY_JWT_SIGNINGKEY, signatureSecret.toJSONString());
76+
77+
UUID accountId = UUID.randomUUID();
78+
UUID playerId = UUID.randomUUID();
79+
KumiteUser user = KumiteUser.builder()
80+
.accountId(accountId)
81+
.playerId(playerId)
82+
.rawRaw(IKumiteTestConstants.userRawRaw())
83+
.details(IKumiteTestConstants.userDetails())
84+
.build();
85+
String accessToken = tokenService.get()
86+
.generateAccessToken(user.getAccountId(), Set.of(playerId), Duration.ofMinutes(1), false);
87+
88+
{
89+
JWSVerifier verifier = new MACVerifier((OctetSequenceKey) signatureSecret);
90+
91+
SignedJWT signedJWT = SignedJWT.parse(accessToken);
92+
Assertions.assertThat(signedJWT.verify(verifier)).isTrue();
93+
}
94+
95+
JwtReactiveAuthenticationManager authManager = new JwtReactiveAuthenticationManager(
96+
new KumiteResourceServerConfiguration().jwtDecoder(env, JdkUuidGenerator.INSTANCE));
97+
98+
Authentication auth = authManager.authenticate(new BearerTokenAuthenticationToken(accessToken)).block();
99+
100+
Assertions.assertThat(auth.getPrincipal()).isOfAnyClassIn(Jwt.class);
101+
Jwt jwt = (Jwt) auth.getPrincipal();
102+
Assertions.assertThat(jwt.getSubject()).isEqualTo(accountId.toString());
103+
Assertions.assertThat(jwt.getAudience()).containsExactly("Kumite-Server");
104+
Assertions.assertThat(jwt.getClaimAsString("playerId")).isEqualTo(playerId.toString());
105+
}
70106
}

contest-core/src/main/java/eu/solven/kumite/app/KumiteServerComponentsConfiguration.java

+27-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package eu.solven.kumite.app;
22

3-
import java.util.concurrent.Executor;
3+
import java.util.List;
4+
import java.util.concurrent.ExecutorService;
45
import java.util.concurrent.Executors;
6+
import java.util.stream.Collectors;
7+
import java.util.stream.IntStream;
58

69
import org.greenrobot.eventbus.EventBus;
710
import org.greenrobot.eventbus.Logger;
@@ -15,7 +18,9 @@
1518
import eu.solven.kumite.app.persistence.InMemoryKumiteConfiguration;
1619
import eu.solven.kumite.board.BoardLifecycleManager;
1720
import eu.solven.kumite.board.BoardLifecycleManagerHelper;
21+
import eu.solven.kumite.board.BoardMutator;
1822
import eu.solven.kumite.board.BoardsRegistry;
23+
import eu.solven.kumite.board.EnabledPlayers;
1924
import eu.solven.kumite.board.realtime.RealTimeBoardManager;
2025
import eu.solven.kumite.contest.ContestsRegistry;
2126
import eu.solven.kumite.eventbus.ActivityLogger;
@@ -58,32 +63,41 @@
5863

5964
KumiteAutomatedSpringConfig.class,
6065

66+
EnabledPlayers.class,
6167
BoardLifecycleManagerHelper.class,
6268

6369
})
6470
@Slf4j
6571
public class KumiteServerComponentsConfiguration {
6672

6773
@Bean
68-
@Qualifier(IGameMetadataConstants.TAG_TURNBASED)
69-
public BoardLifecycleManager tbBoardLifecycleManager(BoardLifecycleManagerHelper helper) {
70-
final Executor boardEvolutionExecutor = Executors.newFixedThreadPool(4);
71-
boardEvolutionExecutor
72-
.execute(() -> log.info("This flags the tbThreadPool threadName={}", Thread.currentThread().getName()));
74+
BoardMutator boardMutator(BoardLifecycleManagerHelper helper, EnabledPlayers enabledPlayer) {
75+
int nbThreads = 4;
76+
List<ExecutorService> boardMutationExecutors = IntStream.range(0, nbThreads).mapToObj(i -> {
77+
ExecutorService es = Executors.newSingleThreadExecutor();
78+
es.execute(() -> log.info("This flags the boardMutator threadPool #{} threadName={}",
79+
i,
80+
Thread.currentThread().getName()));
81+
return es;
82+
}).collect(Collectors.toList());
83+
84+
return new BoardMutator(helper, enabledPlayer, boardMutationExecutors);
85+
}
7386

74-
return new BoardLifecycleManager(helper, boardEvolutionExecutor);
87+
// Should we keep different BoardLifecycleManager? For now, we rely on a single complex RealTimeBoardManager
88+
// @Bean
89+
@Qualifier(IGameMetadataConstants.TAG_TURNBASED)
90+
public BoardLifecycleManager tbBoardLifecycleManager(BoardLifecycleManagerHelper helper,
91+
BoardMutator boardMutator) {
92+
return new BoardLifecycleManager(helper, boardMutator);
7593
}
7694

7795
// We can have multiple BoardLifecycleManager/boardEvolutionExecutor as long as a given contest is guaranteed to be
7896
// processed by a single executor
7997
@Bean
8098
@Qualifier(IGameMetadataConstants.TAG_REALTIME)
81-
public RealTimeBoardManager rtBoardLifecycleManager(BoardLifecycleManagerHelper helper) {
82-
final Executor boardEvolutionExecutor = Executors.newFixedThreadPool(4);
83-
boardEvolutionExecutor
84-
.execute(() -> log.info("This flags the rtThreadPool threadName={}", Thread.currentThread().getName()));
85-
86-
return new RealTimeBoardManager(helper, boardEvolutionExecutor);
99+
public RealTimeBoardManager rtBoardLifecycleManager(BoardLifecycleManagerHelper helper, BoardMutator boardMutator) {
100+
return new RealTimeBoardManager(helper, boardMutator);
87101
}
88102

89103
@Bean
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package eu.solven.kumite.board;
2+
3+
import lombok.Builder;
4+
import lombok.Value;
5+
6+
@Value
7+
@Builder
8+
public class BoardAndMetadata implements IHasBoardAndMetadata {
9+
IKumiteBoard board;
10+
BoardDynamicMetadata metadata;
11+
12+
}

contest-core/src/main/java/eu/solven/kumite/board/BoardHandler.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,18 @@ public Mono<ServerResponse> getBoard(ServerRequest request) {
5656
Contest contestMetadata = contest.get(0);
5757

5858
PlayerContestStatus playingPlayer = contestPlayersRegistry.getPlayingPlayer(playerId, contestMetadata);
59-
60-
IKumiteBoard board = boardsRegistry.hasBoard(contestId).get();
6159

62-
ContestView contestView = makeContestView(contestMetadata, playingPlayer, board);
60+
IHasBoardAndMetadata boardAndMetadata = boardsRegistry.getBoardAndMetadata(contestId).get();
61+
62+
ContestView contestView = makeContestView(contestMetadata, playingPlayer, boardAndMetadata);
6363
log.debug("Serving board for contestId={}", contestView.getContestId());
6464

6565
return KumiteHandlerHelper.okAsJson(contestView);
6666
}
6767

6868
private ContestView makeContestView(Contest contestMetadata,
6969
PlayerContestStatus playingPlayer,
70-
IKumiteBoard board) {
70+
IHasBoardAndMetadata boardAndMetadata) {
7171
UUID viewPlayerId;
7272
if (playingPlayer.isPlayerHasJoined()) {
7373
viewPlayerId = playingPlayer.getPlayerId();
@@ -77,11 +77,12 @@ private ContestView makeContestView(Contest contestMetadata,
7777
viewPlayerId = KumitePlayer.PREVIEW_PLAYER_ID;
7878
}
7979

80-
IKumiteBoardView boardView = board.asView(viewPlayerId);
80+
IKumiteBoardView boardView = boardAndMetadata.getBoard().asView(viewPlayerId);
8181

8282
ContestDynamicMetadata dynamicMetadata = Contest.snapshot(contestMetadata).getDynamicMetadata();
8383
ContestView contestView = ContestView.fromView(boardView)
8484
.contestId(contestMetadata.getContestId())
85+
.boardStateId(boardAndMetadata.getMetadata().getBoardStateId())
8586
.playerStatus(playingPlayer)
8687
.dynamicMetadata(dynamicMetadata)
8788

0 commit comments

Comments
 (0)