diff --git a/src/inttest/java/com/faforever/api/user/MeControllerTest.java b/src/inttest/java/com/faforever/api/user/MeControllerTest.java index 1f81df620..ce68b8b06 100644 --- a/src/inttest/java/com/faforever/api/user/MeControllerTest.java +++ b/src/inttest/java/com/faforever/api/user/MeControllerTest.java @@ -1,11 +1,13 @@ package com.faforever.api.user; import com.faforever.api.AbstractIntegrationTest; +import com.faforever.api.security.FafRole; import org.junit.jupiter.api.Test; import java.util.Set; import static com.faforever.api.data.domain.GroupPermission.ROLE_READ_ACCOUNT_PRIVATE_DETAILS; +import static com.faforever.api.data.domain.GroupPermission.ROLE_USER; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -29,6 +31,9 @@ public void withActiveUserGetResult() throws Exception { .andExpect(jsonPath("$.data.attributes.userName", is(AUTH_ACTIVE_USER))) .andExpect(jsonPath("$.data.attributes.email", is("active-user@faforever.com"))) .andExpect(jsonPath("$.data.attributes.permissions", - containsInAnyOrder(ROLE_READ_ACCOUNT_PRIVATE_DETAILS, "ROLE_" + ROLE_READ_ACCOUNT_PRIVATE_DETAILS))); + containsInAnyOrder( + ROLE_READ_ACCOUNT_PRIVATE_DETAILS, FafRole.ROLE_PREFIX + ROLE_READ_ACCOUNT_PRIVATE_DETAILS, + ROLE_USER, FafRole.ROLE_PREFIX + ROLE_USER + ))); } } diff --git a/src/inttest/java/com/faforever/api/utils/OAuthHelper.java b/src/inttest/java/com/faforever/api/utils/OAuthHelper.java index 8d8b0b90c..4140030ee 100644 --- a/src/inttest/java/com/faforever/api/utils/OAuthHelper.java +++ b/src/inttest/java/com/faforever/api/utils/OAuthHelper.java @@ -2,9 +2,9 @@ import com.faforever.api.data.domain.Player; import com.faforever.api.player.PlayerRepository; -import com.faforever.api.security.FafAuthenticationToken; import com.faforever.api.security.FafRole; import com.faforever.api.security.FafScope; +import com.faforever.api.security.FafUserAuthenticationToken; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; import org.springframework.test.web.servlet.request.RequestPostProcessor; @@ -32,7 +32,7 @@ public RequestPostProcessor addBearerTokenForUser(int userId, @NotNull Set urls = new HashSet<>(); - urls.add(URI.create("turn://%s?transport=tcp".formatted(host))); - urls.add(URI.create("turn://%s?transport=udp".formatted(host))); - urls.add(URI.create("turn://%s".formatted(host))); - - coturnServer.setUrls(urls); - coturnServer.setCredentialType("token"); - coturnServer.setCredential(token); - coturnServer.setUsername(tokenName); + userSupplier.get() + .filter(o -> FafUserAuthenticationToken.class.isAssignableFrom(o.getClass())) + .map(FafUserAuthenticationToken.class::cast) + .ifPresent(fafUserAuthenticationToken -> { + // Build hmac verification as described here: + // https://github.com/coturn/coturn/blob/f67326fe3585eafd664720b43c77e142d9bed73c/README.turnserver#L710 + long timestamp = System.currentTimeMillis() / 1000 + fafApiProperties.getCoturn().getTokenLifetimeSeconds(); + String tokenName = String.format("%d:%d", timestamp, fafUserAuthenticationToken.getUserId()); + + String token = Base64.getEncoder().encodeToString(new HmacUtils(HmacAlgorithms.HMAC_SHA_1, coturnServer.getKey()).hmac(tokenName)); + + String host = coturnServer.getHost(); + if (coturnServer.getPort() != null) { + host += ":" + coturnServer.getPort(); + } + + Set urls = new HashSet<>(); + urls.add(URI.create("turn://%s?transport=tcp".formatted(host))); + urls.add(URI.create("turn://%s?transport=udp".formatted(host))); + urls.add(URI.create("turn://%s".formatted(host))); + + coturnServer.setUrls(urls); + coturnServer.setCredentialType("token"); + coturnServer.setCredential(token); + coturnServer.setUsername(tokenName); + }); } } diff --git a/src/main/java/com/faforever/api/data/DataController.java b/src/main/java/com/faforever/api/data/DataController.java index 9b6b5ddc9..d8c89ebbe 100644 --- a/src/main/java/com/faforever/api/data/DataController.java +++ b/src/main/java/com/faforever/api/data/DataController.java @@ -10,7 +10,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @@ -41,7 +40,6 @@ */ @RestController @RequestMapping(path = DataController.PATH_PREFIX) -@Secured("ROLE_USER") public class DataController { public static final String PATH_PREFIX = "/data"; @@ -53,13 +51,10 @@ public DataController(JsonApi jsonApi) { this.jsonApi = jsonApi; } - private static User getPrincipal(final Authentication authentication) { - return new ElideUser(authentication); - } - //!!! No @Transactional - transactions are being handled by Elide @GetMapping(value = {"/{entity}", "/{entity}/**"}, produces = JSON_API_MEDIA_TYPE) @Cacheable(cacheResolver = "elideCacheResolver", keyGenerator = GetCacheKeyGenerator.NAME) + @PreAuthorize("isAuthenticated()") public ResponseEntity get( @RequestParam final MultiValueMap allRequestParams, final HttpServletRequest request, @@ -180,6 +175,10 @@ public ResponseEntity delete( return wrapResponse(response); } + private static User getPrincipal(final Authentication authentication) { + return new ElideUser(authentication); + } + private ResponseEntity wrapResponse(ElideResponse response) { return ResponseEntity.status(response.getStatus()).body(response.getBody()); } diff --git a/src/main/java/com/faforever/api/data/checks/IsClanMembershipDeletable.java b/src/main/java/com/faforever/api/data/checks/IsClanMembershipDeletable.java index 0411abd96..1036be791 100644 --- a/src/main/java/com/faforever/api/data/checks/IsClanMembershipDeletable.java +++ b/src/main/java/com/faforever/api/data/checks/IsClanMembershipDeletable.java @@ -19,7 +19,7 @@ public static class Inline extends OperationCheck { @Override public boolean ok(ClanMembership membership, RequestScope requestScope, Optional changeSpec) { final ElideUser caller = (ElideUser) requestScope.getUser(); - final Integer requesterId = caller.getFafId().orElse(null); + final Integer requesterId = caller.getFafUserId().orElse(null); return !Objects.equals(membership.getPlayer().getId(), membership.getClan().getLeader().getId()) && (membership.getClan().getLeader().getId().equals(requesterId) || membership.getPlayer().getId().equals(requesterId)); diff --git a/src/main/java/com/faforever/api/data/checks/IsEntityOwner.java b/src/main/java/com/faforever/api/data/checks/IsEntityOwner.java index c5ac9466d..cd3dc2742 100644 --- a/src/main/java/com/faforever/api/data/checks/IsEntityOwner.java +++ b/src/main/java/com/faforever/api/data/checks/IsEntityOwner.java @@ -24,7 +24,7 @@ public static class Inline extends OperationCheck { public boolean ok(OwnableEntity entity, RequestScope requestScope, Optional changeSpec) { final ElideUser caller = (ElideUser) requestScope.getUser(); return entity.getEntityOwner() != null - && entity.getEntityOwner().getId().equals(caller.getFafId().orElse(null)); + && entity.getEntityOwner().getId().equals(caller.getFafUserId().orElse(null)); } } } diff --git a/src/main/java/com/faforever/api/data/checks/IsEntityOwnerFilter.java b/src/main/java/com/faforever/api/data/checks/IsEntityOwnerFilter.java index 24302a5b8..31d945b76 100644 --- a/src/main/java/com/faforever/api/data/checks/IsEntityOwnerFilter.java +++ b/src/main/java/com/faforever/api/data/checks/IsEntityOwnerFilter.java @@ -41,7 +41,7 @@ public static class Filter extends FilterExpressionCheck { @Override public FilterExpression getFilterExpression(Type entityClass, RequestScope requestScope) { final ElideUser caller = (ElideUser) requestScope.getUser(); - final int playerId = caller.getFafId().orElse(NON_EXISTING_PLAYER_ID); + final int playerId = caller.getFafUserId().orElse(NON_EXISTING_PLAYER_ID); List pathList = new ArrayList<>(); getOwnerPath(entityClass, pathList); Path path = new Path(pathList); diff --git a/src/main/java/com/faforever/api/data/domain/GroupPermission.java b/src/main/java/com/faforever/api/data/domain/GroupPermission.java index 5240395ce..da635e86e 100644 --- a/src/main/java/com/faforever/api/data/domain/GroupPermission.java +++ b/src/main/java/com/faforever/api/data/domain/GroupPermission.java @@ -58,6 +58,7 @@ public class GroupPermission extends AbstractEntity implements public static final String ROLE_ADMIN_MAP = "ADMIN_MAP"; public static final String ROLE_ADMIN_MOD = "ADMIN_MOD"; public static final String ROLE_WRITE_MESSAGE = "WRITE_MESSAGE"; + public static final String ROLE_USER = "USER"; private String technicalName; private String nameKey; diff --git a/src/main/java/com/faforever/api/data/hook/BanInfoCreateHook.java b/src/main/java/com/faforever/api/data/hook/BanInfoCreateHook.java index b09e5b2b2..939c3decf 100644 --- a/src/main/java/com/faforever/api/data/hook/BanInfoCreateHook.java +++ b/src/main/java/com/faforever/api/data/hook/BanInfoCreateHook.java @@ -15,9 +15,9 @@ public class BanInfoCreateHook implements LifeCycleHook { @Override public void execute(Operation operation, TransactionPhase phase, BanInfo banInfo, RequestScope requestScope, Optional changes) { final ElideUser caller = (ElideUser) requestScope.getUser(); - caller.getFafId().ifPresent(fafId -> { + caller.getFafUserId().ifPresent(playerId -> { final Player callerPlayer = new Player(); - callerPlayer.setId(fafId); + callerPlayer.setId(playerId); banInfo.setAuthor(callerPlayer); }); } diff --git a/src/main/java/com/faforever/api/data/hook/BanInfoRevokeAuthorUpdateHook.java b/src/main/java/com/faforever/api/data/hook/BanInfoRevokeAuthorUpdateHook.java index fd39226f1..8762d9c80 100644 --- a/src/main/java/com/faforever/api/data/hook/BanInfoRevokeAuthorUpdateHook.java +++ b/src/main/java/com/faforever/api/data/hook/BanInfoRevokeAuthorUpdateHook.java @@ -15,9 +15,9 @@ public class BanInfoRevokeAuthorUpdateHook implements LifeCycleHook { @Override public void execute(Operation operation, TransactionPhase phase, BanInfo banInfo, RequestScope requestScope, Optional changes) { final ElideUser caller = (ElideUser) requestScope.getUser(); - caller.getFafId().ifPresent(fafId -> { + caller.getFafUserId().ifPresent(playerId -> { final Player callerPlayer = new Player(); - callerPlayer.setId(fafId); + callerPlayer.setId(playerId); banInfo.setRevokeAuthor(callerPlayer); }); } diff --git a/src/main/java/com/faforever/api/data/hook/ModerationReportHook.java b/src/main/java/com/faforever/api/data/hook/ModerationReportHook.java index 0a369ee20..7bffb56e0 100644 --- a/src/main/java/com/faforever/api/data/hook/ModerationReportHook.java +++ b/src/main/java/com/faforever/api/data/hook/ModerationReportHook.java @@ -16,9 +16,9 @@ public class ModerationReportHook implements LifeCycleHook { @Override public void execute(Operation operation, TransactionPhase phase, ModerationReport moderationReport, RequestScope requestScope, Optional changes) { final ElideUser caller = (ElideUser) requestScope.getUser(); - final Player callerPlayer = caller.getFafId().map(fafId -> { + final Player callerPlayer = caller.getFafUserId().map(fafPlayerId -> { final Player player = new Player(); - player.setId(fafId); + player.setId(fafPlayerId); return player; }).orElse(null); if (operation == Operation.CREATE) { diff --git a/src/main/java/com/faforever/api/player/PlayerService.java b/src/main/java/com/faforever/api/player/PlayerService.java index 6b316edee..b1f512dec 100644 --- a/src/main/java/com/faforever/api/player/PlayerService.java +++ b/src/main/java/com/faforever/api/player/PlayerService.java @@ -4,7 +4,7 @@ import com.faforever.api.error.ApiException; import com.faforever.api.error.Error; import com.faforever.api.error.ErrorCode; -import com.faforever.api.security.FafAuthenticationToken; +import com.faforever.api.security.FafUserAuthenticationToken; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; @@ -18,8 +18,8 @@ public class PlayerService { private final PlayerRepository playerRepository; public Player getPlayer(Authentication authentication) { - if (authentication instanceof FafAuthenticationToken fafAuthenticationToken) { - return playerRepository.findById((fafAuthenticationToken.getUserId())) + if (authentication instanceof FafUserAuthenticationToken fafUserAuthenticationToken) { + return playerRepository.findById((fafUserAuthenticationToken.getUserId())) .orElseThrow(() -> new ApiException(new Error(TOKEN_INVALID))); } throw new ApiException(new Error(TOKEN_INVALID)); diff --git a/src/main/java/com/faforever/api/security/AuditService.java b/src/main/java/com/faforever/api/security/AuditService.java index 760a3a429..e0524c8aa 100644 --- a/src/main/java/com/faforever/api/security/AuditService.java +++ b/src/main/java/com/faforever/api/security/AuditService.java @@ -9,16 +9,27 @@ @Service public class AuditService { - private UserSupplier userSupplier; + private final UserSupplier userSupplier; public AuditService(UserSupplier userSupplier) { this.userSupplier = userSupplier; } public void logMessage(String message) { - final FafAuthenticationToken fafAuthenticationToken = userSupplier.get(); - String extendedMessage = MessageFormat.format("{0} [invoked by User ''{1}'' with id ''{2}'']", - message, fafAuthenticationToken.getUsername(), fafAuthenticationToken.getUserId()); + final String extendedMessage = userSupplier.get() + .map(fafAuthenticationToken -> { + //move to switch pattern matching with java 21 + if (fafAuthenticationToken instanceof FafUserAuthenticationToken fafUserAuthenticationToken) { + return MessageFormat.format("{0} [invoked by User ''{1}'' with id ''{2}'']", + message, fafUserAuthenticationToken.getUsername(), fafUserAuthenticationToken.getUserId()); + } else if (fafAuthenticationToken instanceof FafServiceAuthenticationToken fafServiceAuthenticationToken) { + return MessageFormat.format("{0} [invoked by Service ''{1}'']", + message, fafServiceAuthenticationToken.getServiceName()); + } else { + throw new RuntimeException(); + } + }) + .orElseGet(() -> MessageFormat.format("{0} [invoked by Annonymous user]", message)); log.info(extendedMessage); } } diff --git a/src/main/java/com/faforever/api/security/ElideUser.java b/src/main/java/com/faforever/api/security/ElideUser.java index 3ad49405c..a758e90d2 100644 --- a/src/main/java/com/faforever/api/security/ElideUser.java +++ b/src/main/java/com/faforever/api/security/ElideUser.java @@ -27,7 +27,10 @@ public boolean isInRole(String role) { return fafAuthentication.hasRole(role); } - public Optional getFafId() { - return Optional.ofNullable(fafAuthentication).map(FafAuthenticationToken::getUserId); + public Optional getFafUserId() { + return Optional.ofNullable(fafAuthentication) + .filter(o -> FafUserAuthenticationToken.class.isAssignableFrom(o.getClass())) + .map(FafUserAuthenticationToken.class::cast) + .map(FafUserAuthenticationToken::getUserId); } } diff --git a/src/main/java/com/faforever/api/security/FafAuthenticationConverter.java b/src/main/java/com/faforever/api/security/FafAuthenticationConverter.java index c5c4acb49..9ee0681ef 100644 --- a/src/main/java/com/faforever/api/security/FafAuthenticationConverter.java +++ b/src/main/java/com/faforever/api/security/FafAuthenticationConverter.java @@ -1,12 +1,12 @@ package com.faforever.api.security; -import java.util.List; -import java.util.Map; - import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.oauth2.jwt.Jwt; +import java.util.List; +import java.util.Map; + /** * Jwt converter that reads scopes + custom FAF roles from the token extension. */ @@ -14,16 +14,22 @@ public class FafAuthenticationConverter implements Converter scopes = extractScopes(source); List roles = extractRoles(source); - return new FafAuthenticationToken(userId, username, scopes, roles); + String subject = extractSubject(source); + + try { + int userId = Integer.parseInt(subject); + String username = extractUsername(source); + return new FafUserAuthenticationToken(userId, username, scopes, roles); + } catch (NumberFormatException e) { + return new FafServiceAuthenticationToken(subject, scopes); + } } - private int extractUserId(Jwt source) { - return Integer.parseInt(source.getSubject()); + private String extractSubject(Jwt source) { + return source.getSubject(); } private String extractUsername(Jwt source) { diff --git a/src/main/java/com/faforever/api/security/FafAuthenticationToken.java b/src/main/java/com/faforever/api/security/FafAuthenticationToken.java index f8a405eaf..06c108837 100644 --- a/src/main/java/com/faforever/api/security/FafAuthenticationToken.java +++ b/src/main/java/com/faforever/api/security/FafAuthenticationToken.java @@ -9,16 +9,13 @@ import java.util.Collection; @Getter -public class FafAuthenticationToken extends AbstractAuthenticationToken { +public abstract sealed class FafAuthenticationToken extends AbstractAuthenticationToken + permits FafUserAuthenticationToken, FafServiceAuthenticationToken { - private final int userId; - private final String username; - private final Collection scopes; - private final Collection roles; + protected final Collection scopes; + protected final Collection roles; public FafAuthenticationToken( - int userId, - String username, @NotNull Collection scopes, @NotNull Collection roles ) { @@ -26,12 +23,8 @@ public FafAuthenticationToken( ImmutableList.builder() .addAll(scopes) .addAll(roles) - // ROLE_USER is an implicit role by Spring Security usually set during regular authentication, so we add it too - .add((GrantedAuthority) () -> "ROLE_USER") .build() ); - this.userId = userId; - this.username = username; this.scopes = scopes; this.roles = roles; // since the access token was already verified, each FafAuthenticationToken is implicitly authenticated @@ -43,16 +36,6 @@ public Object getCredentials() { return null; } - @Override - public Object getPrincipal() { - return userId; - } - - @Override - public String getName() { - return String.format("User %s", userId); - } - public boolean hasRole(String role) { return roles.stream() .anyMatch(r -> r.matches(role)); diff --git a/src/main/java/com/faforever/api/security/FafServiceAuthenticationToken.java b/src/main/java/com/faforever/api/security/FafServiceAuthenticationToken.java new file mode 100644 index 000000000..8f28e3658 --- /dev/null +++ b/src/main/java/com/faforever/api/security/FafServiceAuthenticationToken.java @@ -0,0 +1,38 @@ +package com.faforever.api.security; + +import com.google.common.collect.ImmutableList; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +@Getter +public final class FafServiceAuthenticationToken extends FafAuthenticationToken { + + private final String serviceName; + + public FafServiceAuthenticationToken( + String serviceName, + @NotNull Collection scopes + ) { + super( + scopes, + ImmutableList.builder() + .add(new FafRole("SERVICE")) + .build() + ); + this.serviceName = serviceName; + // since the access token was already verified, each FafAuthenticationToken is implicitly authenticated + this.setAuthenticated(true); + } + + @Override + public Object getPrincipal() { + return serviceName; + } + + @Override + public String getName() { + return String.format("Service %s", serviceName); + } +} diff --git a/src/main/java/com/faforever/api/security/FafUserAuthenticationToken.java b/src/main/java/com/faforever/api/security/FafUserAuthenticationToken.java new file mode 100644 index 000000000..86f9617f7 --- /dev/null +++ b/src/main/java/com/faforever/api/security/FafUserAuthenticationToken.java @@ -0,0 +1,43 @@ +package com.faforever.api.security; + +import com.google.common.collect.ImmutableList; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +@Getter +public final class FafUserAuthenticationToken extends FafAuthenticationToken { + + private final int userId; + private final String username; + + public FafUserAuthenticationToken( + int userId, + String username, + @NotNull Collection scopes, + @NotNull Collection roles + ) { + super( + scopes, + ImmutableList.builder() + .addAll(roles) + .add(new FafRole("USER")) + .build() + ); + this.userId = userId; + this.username = username; + // since the access token was already verified, each FafAuthenticationToken is implicitly authenticated + this.setAuthenticated(true); + } + + @Override + public Object getPrincipal() { + return userId; + } + + @Override + public String getName() { + return String.format("User %s", userId); + } +} diff --git a/src/main/java/com/faforever/api/security/UserSupplier.java b/src/main/java/com/faforever/api/security/UserSupplier.java index f5ab69da0..c2840f348 100644 --- a/src/main/java/com/faforever/api/security/UserSupplier.java +++ b/src/main/java/com/faforever/api/security/UserSupplier.java @@ -3,19 +3,17 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; -import java.util.List; -import java.util.function.Supplier; +import java.util.Optional; @Component -public class UserSupplier implements Supplier { +public class UserSupplier { - @Override - public FafAuthenticationToken get() { + public Optional get() { Object principal = SecurityContextHolder.getContext().getAuthentication(); if (principal instanceof FafAuthenticationToken fafAuthenticationToken) { - return fafAuthenticationToken; + return Optional.of(fafAuthenticationToken); } else { - return new FafAuthenticationToken(-1, "[No User]", List.of(), List.of()); + return Optional.empty(); } } } diff --git a/src/main/java/com/faforever/api/security/elide/permission/FafUserCheck.java b/src/main/java/com/faforever/api/security/elide/permission/FafUserCheck.java index 468aa5742..7132d3af4 100644 --- a/src/main/java/com/faforever/api/security/elide/permission/FafUserCheck.java +++ b/src/main/java/com/faforever/api/security/elide/permission/FafUserCheck.java @@ -66,7 +66,7 @@ protected boolean checkUserPermission(User abstractUser, String... userPermissio if (missedRoles.isEmpty()) { log.trace("All requested permissions are granted: {}", String.join(", ", userPermissionRoles)); } else { - log.debug("Permissions '{}' are not granted in requested permissions: {} , for user with id: {}", String.join(", ", missedRoles), String.join(", ", grantedUserRoles), user.getFafId()); + log.debug("Permissions '{}' are not granted in requested permissions: {} , for user with id: {}", String.join(", ", missedRoles), String.join(", ", grantedUserRoles), user.getFafUserId()); } } diff --git a/src/main/java/com/faforever/api/user/MeController.java b/src/main/java/com/faforever/api/user/MeController.java index beb4ecb0f..8bf23e2bc 100644 --- a/src/main/java/com/faforever/api/user/MeController.java +++ b/src/main/java/com/faforever/api/user/MeController.java @@ -3,7 +3,7 @@ import com.faforever.api.data.domain.Player; import com.faforever.api.data.domain.UserGroup; import com.faforever.api.player.PlayerService; -import com.faforever.api.security.FafAuthenticationToken; +import com.faforever.api.security.FafUserAuthenticationToken; import com.faforever.api.security.UserSupplier; import com.yahoo.elide.jsonapi.models.Data; import com.yahoo.elide.jsonapi.models.JsonApiDocument; @@ -38,42 +38,46 @@ public class MeController { @ApiResponse(responseCode = "200", description = "Success with JsonApi compliant MeResult") @Secured("ROLE_USER") public JsonApiDocument me() { - FafAuthenticationToken authentication = userSupplier.get(); + return userSupplier.get() + .filter(o -> FafUserAuthenticationToken.class.isAssignableFrom(o.getClass())) + .map(FafUserAuthenticationToken.class::cast) + .map(authentication -> { + Player player = playerService.getById(authentication.getUserId()); + Set grantedAuthorities = authentication.getRoles().stream() + //.map(FafRole::role) + // TEMPORARY WORKAROUND: we stripped away the ROLE_ prefix, but clients need to adapt. Until then, we add both + .flatMap(role -> Stream.of(role.role(), role.getAuthority())) + .collect(Collectors.toSet()); - Player player = playerService.getById(authentication.getUserId()); - Set grantedAuthorities = authentication.getRoles().stream() - //.map(FafRole::role) - // TEMPORARY WORKAROUND: we stripped away the ROLE_ prefix, but clients need to adapt. Until then, we add both - .flatMap( role -> Stream.of(role.role(), role.getAuthority()) ) - .collect(Collectors.toSet()); + Set groups = player.getUserGroups().stream() + .map(UserGroup::getTechnicalName) + .collect(Collectors.toSet()); - Set groups = player.getUserGroups().stream() - .map(UserGroup::getTechnicalName) - .collect(Collectors.toSet()); - - return new JsonApiDocument(new Data<>( - new Resource("me", - "me", - null, - Map.of( - "userId", player.getId(), - "userName", player.getLogin(), - "email", player.getEmail(), - "clan", player.getClan() == null ? Optional.empty() : Clan.builder() - .id(player.getClan().getId()) - .membershipId(player.getClanMembership().getId()) - .tag(player.getClan().getTag()) - .name(player.getClan().getName()) - .build(), - "lastLogin", Optional.ofNullable(player.getLastLogin()), - "groups", groups, - "permissions", grantedAuthorities - ), - null, - null, - null - ) - )); + return new JsonApiDocument(new Data<>( + new Resource("me", + "me", + null, + Map.of( + "userId", player.getId(), + "userName", player.getLogin(), + "email", player.getEmail(), + "clan", player.getClan() == null ? Optional.empty() : Clan.builder() + .id(player.getClan().getId()) + .membershipId(player.getClanMembership().getId()) + .tag(player.getClan().getTag()) + .name(player.getClan().getName()) + .build(), + "lastLogin", Optional.ofNullable(player.getLastLogin()), + "groups", groups, + "permissions", grantedAuthorities + ), + null, + null, + null + ) + )); + } + ).orElseGet(JsonApiDocument::new); } @Value diff --git a/src/main/java/com/faforever/api/user/UserService.java b/src/main/java/com/faforever/api/user/UserService.java index bcf8a406e..e66bce156 100644 --- a/src/main/java/com/faforever/api/user/UserService.java +++ b/src/main/java/com/faforever/api/user/UserService.java @@ -10,10 +10,10 @@ import com.faforever.api.error.Error; import com.faforever.api.error.ErrorCode; import com.faforever.api.player.PlayerRepository; -import com.faforever.api.security.FafAuthenticationToken; import com.faforever.api.security.FafPasswordEncoder; import com.faforever.api.security.FafTokenService; import com.faforever.api.security.FafTokenType; +import com.faforever.api.security.FafUserAuthenticationToken; import com.google.common.hash.Hashing; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; @@ -348,8 +348,8 @@ private void setPassword(User user, String password) { } public User getUser(Authentication authentication) { - if (authentication instanceof FafAuthenticationToken fafAuthenticationToken) { - return getUser(fafAuthenticationToken.getUserId()); + if (authentication instanceof FafUserAuthenticationToken fafUserAuthenticationToken) { + return getUser(fafUserAuthenticationToken.getUserId()); } throw ApiException.of(TOKEN_INVALID); }