Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/#394-solr-config' into #394-solr…
Browse files Browse the repository at this point in the history
…-config

# Conflicts:
#	src/main/java/com/faforever/api/data/domain/package-info.java
  • Loading branch information
ahsanbagwan committed Feb 26, 2021
2 parents 02887f0 + 51c6e86 commit 5f9d0e3
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 44 deletions.
18 changes: 0 additions & 18 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/inttest/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ faf-api:
jwt:
secret-key-path: test-pki-private.key
public-key-path: test-pki-public.key
faf-hydra-jwks-url: https://accounts.google.com/.well-known/openid-configuration
faf-hydra-issuer: https://hydra.test.faforever.com/
map:
target-directory: "build/cache/map/maps"
directory-preview-path-small: "build/cache/map_previews/small"
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/faforever/api/config/FafApiProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public static class Jwt {
private Path publicKeyPath;
private int accessTokenValiditySeconds = 3600;
private int refreshTokenValiditySeconds = 3600;
private String fafHydraJwksUrl;
private String fafHydraIssuer;
}

@Data
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.faforever.api.config.security.oauth2;

import com.faforever.api.config.FafApiProperties;
import com.faforever.api.security.FafMultiTokenStore;
import com.faforever.api.security.FafUserAuthenticationConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -9,7 +10,6 @@
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.io.IOException;
import java.nio.file.Files;
Expand All @@ -33,8 +33,8 @@ public DefaultTokenServices tokenServices(TokenStore tokenStore) {
}

@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
public TokenStore tokenStore(FafApiProperties properties, JwtAccessTokenConverter jwtAccessTokenConverter) {
return new FafMultiTokenStore(properties, jwtAccessTokenConverter);
}

@Bean
Expand Down
29 changes: 24 additions & 5 deletions src/main/java/com/faforever/api/data/domain/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import javax.persistence.Table;
import javax.persistence.Transient;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Set;

@Entity
@Table(name = "game_stats")
Expand All @@ -47,9 +47,9 @@ public class Game {
private MapVersion mapVersion;
private String name;
private Validity validity;
private List<GamePlayerStats> playerStats;
private Set<GamePlayerStats> playerStats;
private String replayUrl;
private List<GameReview> reviews;
private Set<GameReview> reviews;
private GameReviewsSummary reviewsSummary;

@Id
Expand Down Expand Up @@ -105,7 +105,7 @@ public Validity getValidity() {

@OneToMany(mappedBy = "game")
@BatchSize(size = 1000)
public List<GamePlayerStats> getPlayerStats() {
public Set<GamePlayerStats> getPlayerStats() {
return playerStats;
}

Expand All @@ -124,7 +124,7 @@ public String getReplayUrl() {
@OneToMany(mappedBy = "game")
@UpdatePermission(expression = Prefab.ALL)
@BatchSize(size = 1000)
public List<GameReview> getReviews() {
public Set<GameReview> getReviews() {
return reviews;
}

Expand All @@ -135,4 +135,23 @@ public List<GameReview> getReviews() {
public GameReviewsSummary getReviewsSummary() {
return reviewsSummary;
}

/**
* This ManyToOne relationship leads to a double left outer join through Elide causing an additional full table
* scan on the matchmaker_queue table. Even though it has only 3 records, it causes MySql 5.7 and MySQL to run
* a list of all games > 1 min on prod where it was ~1 second before.
*
* This can be fixed by migrating to MariaDB.
*/
// private Integer matchmakerQueueId;
//
// @JoinTable(name = "matchmaker_queue_game",
// joinColumns = @JoinColumn(name = "game_stats_id"),
// inverseJoinColumns = @JoinColumn(name = "matchmaker_queue_id")
// )
// @ManyToOne(fetch = FetchType.LAZY)
// @Nullable
// public Integer getMatchmakerQueueId() {
// return matchmakerQueueId;
// }
}
18 changes: 9 additions & 9 deletions src/main/java/com/faforever/api/data/domain/Leaderboard.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,22 @@ public class Leaderboard extends AbstractEntity {

public static final String TYPE_NAME = "leaderboard";

private String technical_name;
private String name_key;
private String description_key;
private String technicalName;
private String nameKey;
private String descriptionKey;

@Column(name = "technical_name")
public String getTechnical_name() {
return technical_name;
public String getTechnicalName() {
return technicalName;
}

@Column(name = "name_key")
public String getName_key() {
return name_key;
public String getNameKey() {
return nameKey;
}

@Column(name = "description_key")
public String getDescription_key() {
return description_key;
public String getDescriptionKey() {
return descriptionKey;
}
}
123 changes: 123 additions & 0 deletions src/main/java/com/faforever/api/security/FafMultiTokenStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.faforever.api.security;

import com.faforever.api.config.FafApiProperties;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.util.JsonParser;
import org.springframework.security.oauth2.common.util.JsonParserFactory;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;

@Component
public class FafMultiTokenStore implements TokenStore {
private JsonParser objectMapper = JsonParserFactory.create();

/**
* The token store for token created by this API (using custom OAuth 2.0 without OpenID Connect)
*/
private final JwtTokenStore classicTokenStore;

/**
* The token store for tokens created by Ory Hydra (OpenID Connect token customized for FAF)
*/
private final JwkTokenStore hydraTokenStore;

private final FafApiProperties fafApiProperties;

public FafMultiTokenStore(FafApiProperties fafApiProperties, JwtAccessTokenConverter jwtAccessTokenConverter) {
this.fafApiProperties = fafApiProperties;

classicTokenStore = new JwtTokenStore(jwtAccessTokenConverter);
hydraTokenStore = new JwkTokenStore(fafApiProperties.getJwt().getFafHydraJwksUrl(), jwtAccessTokenConverter);
}

@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
return readAuthentication(token.getValue());
}

@Override
public OAuth2Authentication readAuthentication(String token) {
Jwt unverifiedJwt = JwtHelper.decode(token);
Map<String, Object> claims = objectMapper.parseMap(unverifiedJwt.getClaims());

if (Objects.equals(claims.get("iss"), fafApiProperties.getJwt().getFafHydraIssuer())) {
return hydraTokenStore.readAuthentication(token);
} else {
return classicTokenStore.readAuthentication(token);
}
}

@Override
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
// no implementation, equal to JwtTokenStore
}

@Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
try {
return classicTokenStore.readAccessToken(tokenValue);
} catch (Exception e) {
return hydraTokenStore.readAccessToken(tokenValue);
}
}

@Override
public void removeAccessToken(OAuth2AccessToken token) {
// no implementation, equal to JwtTokenStore
}

@Override
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
// no implementation, equal to JwtTokenStore
}

@Override
public OAuth2RefreshToken readRefreshToken(String tokenValue) {
return null;
}

@Override
public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
return null;
}

@Override
public void removeRefreshToken(OAuth2RefreshToken token) {
// no implementation, equal to JwtTokenStore
}

@Override
public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
// no implementation, equal to JwtTokenStore
}

@Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
// equal to JwtTokenStore
return null;
}

@Override
public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
// equal to JwtTokenStore
return Collections.emptySet();
}

@Override
public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
// equal to JwtTokenStore
return Collections.emptySet();
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package com.faforever.api.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* Converts a {@link FafUserDetails} from and to an {@link Authentication} for use in a JWT token.
*/
@Slf4j
public class FafUserAuthenticationConverter extends DefaultUserAuthenticationConverter {

public static final String USER_ID_KEY = "user_id";
Expand All @@ -32,16 +36,36 @@ public class FafUserAuthenticationConverter extends DefaultUserAuthenticationCon

@Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (!map.containsKey(USER_ID_KEY)) {
return null;
}
if (map.containsKey(USER_ID_KEY)) {
log.debug("Access token is FAF legacy token");

int id = (Integer) map.get(USER_ID_KEY);
String username = (String) map.get(USERNAME);
boolean accountNonLocked = Optional.ofNullable((Boolean) map.get(NON_LOCKED)).orElse(true);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
UserDetails user = new FafUserDetails(id, username, "N/A", accountNonLocked, authorities);

return new UsernamePasswordAuthenticationToken(user, "N/A", authorities);
} else {
Object sub = map.get("sub");

if (sub == null) {
log.debug("Access token has no user associated");
return null;
}

int id = (Integer) map.get(USER_ID_KEY);
String username = (String) map.get(USERNAME);
boolean accountNonLocked = Optional.ofNullable((Boolean) map.get(NON_LOCKED)).orElse(true);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
UserDetails user = new FafUserDetails(id, username, "N/A", accountNonLocked, authorities);
log.debug("Access token is FAF OpenID Connect token");
int id = Integer.parseInt((String) sub);
var ext = (Map<String, Object>) map.get("ext");
var roles = (List<String>) ext.get("roles");

var authorities = roles.stream()
.map(role -> (GrantedAuthority) () -> "ROLE_" + role)
.collect(Collectors.toList());

UserDetails user = new FafUserDetails(id, "username", "N/A", false, authorities);
return new UsernamePasswordAuthenticationToken(user, "N/A", authorities);
}

return new UsernamePasswordAuthenticationToken(user, "N/A", authorities);
}
}
2 changes: 2 additions & 0 deletions src/main/resources/config/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ faf-api:
jwt:
secretKeyPath: ${JWT_PRIVATE_KEY_PATH:test-pki-private.key}
publicKeyPath: ${JWT_PUBLIC_KEY_PATH:test-pki-public.key}
fafHydraJwksUrl: ${JWT_FAF_HYDRA_JWKS_URL:https://hydra.test.faforever.com/.well-known/jwks.json}
fafHydraIssuer: ${JWT_FAF_HYDRA_ISSUER:https://hydra.test.faforever.com/}
map:
target-directory: ${MAP_UPLOAD_PATH:build/cache/map/maps}
directory-preview-path-small: ${MAP_PREVIEW_PATH_SMALL:build/cache/map_previews/small}
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/config/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ faf-api:
jwt:
secretKeyPath: ${JWT_PRIVATE_KEY_PATH}
publicKeyPath: ${JWT_PUBLIC_KEY_PATH}
fafHydraJwksUrl: ${JWT_FAF_HYDRA_JWKS_URL}
fafHydraIssuer: ${JWT_FAF_HYDRA_ISSUER}
map:
target-directory: ${MAP_UPLOAD_PATH}
directory-preview-path-small: ${MAP_PREVIEW_PATH_SMALL}
Expand Down

0 comments on commit 5f9d0e3

Please sign in to comment.