Skip to content

Commit

Permalink
Merge branch 'release/0.5.6'
Browse files Browse the repository at this point in the history
  • Loading branch information
micheljung committed May 16, 2017
2 parents 6fd3123 + e97f2aa commit d4d4187
Show file tree
Hide file tree
Showing 40 changed files with 524 additions and 350 deletions.
7 changes: 5 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'propdeps'

group = 'micheljung'
version = '0.5.5'
group = 'faforever'
version = '0.5.6'

sourceCompatibility = 1.8
targetCompatibility = 1.8
Expand Down Expand Up @@ -158,6 +158,7 @@ dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-jetty")
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
compile("de.codecentric:spring-boot-admin-starter-client:${springBootAdminClientVersion}")

compile("com.github.FAForever:faf-java-commons:${fafCommonsVersion}")
Expand All @@ -171,8 +172,10 @@ dependencies {
compile("com.google.guava:guava:${guavaVersion}")
compile("io.springfox:springfox-swagger-ui:${swaggerVersion}")
compile("io.springfox:springfox-swagger2:${swaggerVersion}")
compile("io.swagger:swagger-core:${swaggerCoreVersion}")
compile("javax.inject:javax.inject:${javaxInjectVersion}")
compile("com.yahoo.elide:elide-core:${elideVersion}")
compile("com.yahoo.elide:elide-swagger:${elideVersion}")
compile("com.yahoo.elide:elide-datastore-hibernate5:${elideVersion}")
compile("org.hibernate:hibernate-java8:${hibernateVersion}")
compile("com.zaxxer:HikariCP:${hikariCpVersion}") {
Expand Down
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mysqlConnectorVersion=6.0.5
gradleDockerVersion=3.0.4
propdepsVersion=0.0.7
swaggerVersion=2.6.1
swaggerCoreVersion=1.5.8
coverallsGradlePluginVersion=2.4.0
hikariCpVersion=2.4.6
springSecurityJwtVersion=1.0.7.RELEASE
Expand All @@ -31,3 +32,4 @@ lutungVersion=0.0.7
commonsCompressVersion=1.13
jsonPath=2.2.0
jsonPathAssert=2.2.0
thymeleafVersion=3.0.5.RELEASE
49 changes: 18 additions & 31 deletions src/main/java/com/faforever/api/clan/ClanService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.faforever.api.clan;

import com.faforever.api.clan.result.ClanResult;
import com.faforever.api.clan.result.InvitationResult;
import com.faforever.api.clan.result.PlayerResult;
import com.faforever.api.config.FafApiProperties;
import com.faforever.api.data.domain.Clan;
import com.faforever.api.data.domain.ClanMembership;
Expand All @@ -11,9 +14,7 @@
import com.faforever.api.player.PlayerRepository;
import com.faforever.api.player.PlayerService;
import com.faforever.api.security.JwtService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import lombok.SneakyThrows;
import org.springframework.security.core.Authentication;
import org.springframework.security.jwt.Jwt;
Expand Down Expand Up @@ -53,7 +54,7 @@ public ClanService(ClanRepository clanRepository,
}

@SneakyThrows
public Clan create(String name, String tag, String description, Player creator) {
Clan create(String name, String tag, String description, Player creator) {
if (!creator.getClanMemberships().isEmpty()) {
throw new ApiException(new Error(ErrorCode.CLAN_CREATE_CREATOR_IS_IN_A_CLAN));
}
Expand Down Expand Up @@ -84,7 +85,7 @@ public Clan create(String name, String tag, String description, Player creator)
}

@SneakyThrows
public String generatePlayerInvitationToken(Player requester, int newMemberId, int clanId) {
String generatePlayerInvitationToken(Player requester, int newMemberId, int clanId) {
Clan clan = clanRepository.findOne(clanId);

if (clan == null) {
Expand All @@ -100,39 +101,34 @@ public String generatePlayerInvitationToken(Player requester, int newMemberId, i
}

long expire = Instant.now()
.plus(fafApiProperties.getClan().getInviteLinkExpireDurationMinutes(), ChronoUnit.MINUTES)
.toEpochMilli();

return jwtService.sign(
ImmutableMap.of(JwtKeys.NEW_MEMBER_ID, newMemberId,
JwtKeys.EXPIRE_IN, expire,
JwtKeys.CLAN, ImmutableMap.of(
JwtKeys.CLAN_ID, clan.getId(),
JwtKeys.CLAN_TAG, clan.getTag(),
JwtKeys.CLAN_NAME, clan.getName())
));
.plus(fafApiProperties.getClan().getInviteLinkExpireDurationMinutes(), ChronoUnit.MINUTES)
.toEpochMilli();

InvitationResult result = new InvitationResult(expire,
ClanResult.of(clan),
PlayerResult.of(newMember));
return jwtService.sign(result);
}

@SneakyThrows
// TODO @dragonfire don't manually read JSON values, deserialize into a Java object?
public void acceptPlayerInvitationToken(String stringToken, Authentication authentication) {
void acceptPlayerInvitationToken(String stringToken, Authentication authentication) {
Jwt token = jwtService.decodeAndVerify(stringToken);
JsonNode data = objectMapper.readTree(token.getClaims());
InvitationResult invitation = objectMapper.readValue(token.getClaims(), InvitationResult.class);

if (data.get(JwtKeys.EXPIRE_IN).asLong() < System.currentTimeMillis()) {
if (invitation.isExpired()) {
throw new ApiException(new Error(ErrorCode.CLAN_ACCEPT_TOKEN_EXPIRE));
}

Player player = playerService.getPlayer(authentication);
Clan clan = clanRepository.findOne(data.get(JwtKeys.CLAN).get(JwtKeys.CLAN_ID).asInt());
Clan clan = clanRepository.findOne(invitation.getClan().getId());

if (clan == null) {
throw new ApiException(new Error(ErrorCode.CLAN_NOT_EXISTS));
}

Player newMember = playerRepository.findOne(data.get(JwtKeys.NEW_MEMBER_ID).asInt());
Player newMember = playerRepository.findOne(invitation.getNewMember().getId());
if (newMember == null) {
throw new ProgrammingError("ClanMember does not exist: " + data.get(JwtKeys.NEW_MEMBER_ID).asInt());
throw new ProgrammingError("ClanMember does not exist: " + invitation.getNewMember().getId());
}

if (player.getId() != newMember.getId()) {
Expand All @@ -147,13 +143,4 @@ public void acceptPlayerInvitationToken(String stringToken, Authentication authe
membership.setPlayer(newMember);
clanMembershipRepository.save(membership);
}

private class JwtKeys {
public static final String NEW_MEMBER_ID = "newMemberId";
public static final String EXPIRE_IN = "expire";
public static final String CLAN = "clan";
public static final String CLAN_ID = "id";
public static final String CLAN_TAG = "tag";
public static final String CLAN_NAME = "name";
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/faforever/api/clan/ClansController.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ public MeResult me(Authentication authentication) {
: null;
ClanResult clanResult = null;
if (clan != null) {
clanResult = new ClanResult(clan.getId(), clan.getTag(), clan.getName());
clanResult = ClanResult.of(clan);
}
return new MeResult(new PlayerResult(player.getId(), player.getLogin()), clanResult);
return new MeResult(PlayerResult.of(player), clanResult);
}

// This request cannot be handled by JSON API because we must simultaneously create two resources (a,b)
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/com/faforever/api/clan/result/ClanResult.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.faforever.api.clan.result;

import com.faforever.api.data.domain.Clan;
import lombok.Data;

@Data
public class ClanResult {
private final int id;
private final Integer id;
private final String tag;
private final String name;

public static ClanResult of(Clan clan) {
return new ClanResult(clan.getId(), clan.getTag(), clan.getName());
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/faforever/api/clan/result/InvitationResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.faforever.api.clan.result;

import lombok.Data;

@Data
public class InvitationResult {
private final long expire;
private final ClanResult clan;
private final PlayerResult newMember;

public boolean isExpired() {
return expire < System.currentTimeMillis();
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/faforever/api/clan/result/PlayerResult.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.faforever.api.clan.result;

import com.faforever.api.data.domain.Player;
import lombok.Data;

@Data
public class PlayerResult {
private final int id;
private final String login;

public static PlayerResult of(Player newMember) {
return new PlayerResult(newMember.getId(), newMember.getLogin());
}
}
25 changes: 25 additions & 0 deletions src/main/java/com/faforever/api/config/MvcConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.faforever.api.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@EnableWebMvc
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
22 changes: 13 additions & 9 deletions src/main/java/com/faforever/api/config/elide/ElideConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,7 @@
public class ElideConfig {

@Bean
public Elide elide(EntityManagerFactory entityManagerFactory, ObjectMapper objectMapper) {
ConcurrentHashMap<String, Class<? extends Check>> checks = new ConcurrentHashMap<>();
checks.put(IsAuthenticated.EXPRESSION, IsAuthenticated.Inline.class);
checks.put(IsLoginOwner.EXPRESSION, IsLoginOwner.Inline.class);
checks.put(IsReviewOwner.EXPRESSION, IsReviewOwner.Inline.class);
checks.put(IsClanLeader.EXPRESSION, IsClanLeader.Inline.class);
checks.put(IsClanMembershipDeletable.EXPRESSION, IsClanMembershipDeletable.Inline.class);

EntityDictionary entityDictionary = new EntityDictionary(checks);
public Elide elide(EntityManagerFactory entityManagerFactory, ObjectMapper objectMapper, EntityDictionary entityDictionary) {
RSQLFilterDialect rsqlFilterDialect = new RSQLFilterDialect(entityDictionary);

HibernateStore hibernateStore = new Builder(entityManagerFactory.unwrap(SessionFactory.class)).build();
Expand All @@ -57,6 +49,18 @@ public Elide elide(EntityManagerFactory entityManagerFactory, ObjectMapper objec
.build());
}

@Bean
public EntityDictionary entityDictionary() {
ConcurrentHashMap<String, Class<? extends Check>> checks = new ConcurrentHashMap<>();
checks.put(IsAuthenticated.EXPRESSION, IsAuthenticated.Inline.class);
checks.put(IsLoginOwner.EXPRESSION, IsLoginOwner.Inline.class);
checks.put(IsReviewOwner.EXPRESSION, IsReviewOwner.Inline.class);
checks.put(IsClanLeader.EXPRESSION, IsClanLeader.Inline.class);
checks.put(IsClanMembershipDeletable.EXPRESSION, IsClanMembershipDeletable.Inline.class);

return new EntityDictionary(checks);
}

/**
* Returns a cache resolver that resolves cache names by JSON API type names. For instance, the type "map" will be
* resolved to a cache named "map".
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.faforever.api.config.elide;

import com.faforever.api.data.DataController;
import com.yahoo.elide.contrib.swagger.SwaggerBuilder;
import com.yahoo.elide.core.EntityDictionary;
import io.swagger.models.Info;
import io.swagger.models.Swagger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import springfox.documentation.annotations.ApiIgnore;
import springfox.documentation.spring.web.json.Json;
import springfox.documentation.spring.web.json.JsonSerializer;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

@Controller
public class ElideSwaggerController {

private JsonSerializer jsonSerializer;
private EntityDictionary entityDictionary;

@Autowired
public ElideSwaggerController(JsonSerializer jsonSerializer, EntityDictionary entityDictionary) {
this.jsonSerializer = jsonSerializer;
this.entityDictionary = entityDictionary;
}

@ApiIgnore
@RequestMapping(value = "/elide/docs",
method = RequestMethod.GET, produces = {APPLICATION_JSON_VALUE})
public
@ResponseBody
ResponseEntity<Json> getDocumentation() {
Info info = new Info().title("Elide JSON API").version("1.0");
SwaggerBuilder builder = new SwaggerBuilder(entityDictionary, info);
Swagger document = builder.build()
.basePath(DataController.PATH_PREFIX);

return new ResponseEntity<Json>(jsonSerializer.toJson(document), HttpStatus.OK);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // http://stackoverflow.com/a/29917946
.headers()
.cacheControl().disable()
.and().formLogin().permitAll()
.cacheControl().disable()
.and().formLogin().loginPage("/login").permitAll()
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/oauth/**").permitAll()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.faforever.api.config.swagger;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.spring.web.DocumentationCache;
import springfox.documentation.swagger.web.InMemorySwaggerResourcesProvider;
import springfox.documentation.swagger.web.SwaggerResource;

import java.util.List;

@Component
@Primary
public class ElideSwaggerResourceProvider extends InMemorySwaggerResourcesProvider {
public ElideSwaggerResourceProvider(DocumentationCache documentationCache) {
super(documentationCache);
}

@Override
public List<SwaggerResource> get() {
final List<SwaggerResource> swaggerResources = super.get();

SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName("data");
swaggerResource.setLocation("/elide/docs");
swaggerResource.setSwaggerVersion("2.0");
swaggerResources.add(swaggerResource);
return swaggerResources;
}
}
35 changes: 35 additions & 0 deletions src/main/java/com/faforever/api/data/domain/AbstractEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.faforever.api.data.domain;

import lombok.Setter;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import java.time.OffsetDateTime;

@MappedSuperclass
@Setter
public abstract class AbstractEntity {
private Integer id;
private OffsetDateTime createTime;
private OffsetDateTime updateTime;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
public Integer getId() {
return id;
}

@Column(name = "create_time")
public OffsetDateTime getCreateTime() {
return createTime;
}

@Column(name = "update_time")
public OffsetDateTime getUpdateTime() {
return updateTime;
}
}

0 comments on commit d4d4187

Please sign in to comment.