Skip to content

Commit

Permalink
Merge pull request #52 from ACCULA/develop
Browse files Browse the repository at this point in the history
Release v1.0
  • Loading branch information
lamtev authored May 16, 2020
2 parents d7c7ec0 + f5b90e7 commit 2a40c73
Show file tree
Hide file tree
Showing 191 changed files with 20,583 additions and 1 deletion.
85 changes: 85 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
plugins:
sonar-java:
enabled: true
config:
sonar.java.source: "14"
checks:
squid:S1068:
enabled: false # Unused fields. Since we use lombok, the check results so many false positives
squid:S00112:
enabled: false # Use of generic exceptions in method declaration.

pmd:
enabled: true
checks:
AtLeastOneConstructor:
enabled: false # Since we use lombok, it's not actual
LawOfDemeter:
enabled: false # We love Java Stream API and other chaining-friendly libraries!
JUnitTestsShouldIncludeAssert:
enabled: false # Spring Boot tests use the underlying statements inside its own
BeanMembersShouldSerialize:
enabled: false # Most of the declared beans in our app should not be serialized
UseProperClassLoader:
enabled: false
CommentDefaultAccessModifier:
enabled: false
SignatureDeclareThrowsException:
enabled: false
ExcessiveImports:
enabled: false
DefaultPackage:
enabled: false # Lombok-associated issue
CallSuperInConstructor:
enabled: false

config:
file: pmd.xml

checkstyle:
enabled: true
config:
file: checkstyle.xml
checks:
com.puppycrawl.tools.checkstyle.checks.imports.CustomImportOrderCheck:
enabled: false # We separate static imports from usual ones but this check does not expect that and thus warns
com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocParagraphCheck:
enabled: false
com.puppycrawl.tools.checkstyle.checks.javadoc.SummaryJavadocCheck:
enabled: false

checks:
argument-count:
config:
threshold: 5
complex-logic:
config:
threshold: 10
file-lines:
config:
threshold: 550
method-complexity:
config:
threshold: 10
method-count:
config:
threshold: 25
method-lines:
config:
threshold: 55
nested-control-flow:
config:
threshold: 5
return-statements:
config:
threshold: 5
similar-code:
config:
threshold: 50
identical-code:
config:
threshold: 50

exclude_patterns:
- "/web/**"
- "/api/src/test/**"
20 changes: 20 additions & 0 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Java CI with Gradle

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up JDK 14
uses: actions/setup-java@v1
with:
java-version: 14
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build -x web:build

- uses: codecov/codecov-action@v1
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.idea
**/build
*.iml
**/out
/.gradle
*.der
*.pem
application-secrets.*
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# accula
# Advanced Code Clones UniversaL Analyzer

![Java CI with Gradle](https://github.com/ACCULA/accula/workflows/Java%20CI%20with%20Gradle/badge.svg)
[![codecov](https://codecov.io/gh/accula/accula/branch/develop/graph/badge.svg)](https://codecov.io/gh/accula/accula)
[![Maintainability](https://api.codeclimate.com/v1/badges/889a9bb55d985b215e1b/maintainability)](https://codeclimate.com/github/ACCULA/accula/maintainability)
8 changes: 8 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM gradle:jdk14 AS build
WORKDIR /app
COPY . /app
RUN gradle build -x test -x web:build --no-daemon

FROM openjdk:14-alpine
COPY --from=build /app/api/build/libs/*.jar accula.jar
ENTRYPOINT ["java","-jar","/accula.jar"]
39 changes: 39 additions & 0 deletions api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
plugins {
id("org.springframework.boot") version "2.3.0.RC1"
id("io.spring.dependency-management") version "1.0.9.RELEASE"
}

version = "1.0-SNAPSHOT"

repositories {
maven(url = "https://repo.spring.io/milestone")
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter-webflux")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
implementation("org.springframework.boot:spring-boot-starter-security")
testImplementation("org.springframework.security:spring-security-test")

implementation("org.springframework.boot:spring-boot-starter-actuator")

implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
implementation("org.springframework.security:spring-security-oauth2-client")

implementation("com.auth0:java-jwt:3.10.2")

implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
implementation("io.r2dbc:r2dbc-postgresql:0.8.2.RELEASE")
implementation("io.r2dbc:r2dbc-pool:0.8.2.RELEASE")
implementation("io.r2dbc:r2dbc-spi:0.8.1.RELEASE")

implementation("org.postgresql:postgresql")
implementation("org.springframework:spring-jdbc")
implementation("org.flywaydb:flyway-core")

testImplementation("io.zonky.test:embedded-database-spring-test:1.5.3")

annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
}
11 changes: 11 additions & 0 deletions api/src/main/java/org/accula/api/AcculaApiApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.accula.api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AcculaApiApplication {
public static void main(final String[] args) {
SpringApplication.run(AcculaApiApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.accula.api.auth;

import org.accula.api.auth.jwt.AuthorizedUser;
import org.accula.api.auth.jwt.JwtAuthentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import reactor.core.publisher.Mono;

/**
* Provides the current authorized user stored in a {@link org.springframework.security.core.context.SecurityContext}
*
* @author Anton Lamtev
*/
public final class CurrentAuthorizedUserProvider {
private CurrentAuthorizedUserProvider() {
}

public static Mono<AuthorizedUser> get() {
return ReactiveSecurityContextHolder
.getContext()
.flatMap(ctx -> {
final var authentication = ctx.getAuthentication();
if (!(authentication instanceof JwtAuthentication)) {
return Mono.empty();
}

return Mono.just(((JwtAuthentication) authentication).getPrincipal());
});
}
}
11 changes: 11 additions & 0 deletions api/src/main/java/org/accula/api/auth/jwt/AuthorizedUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.accula.api.auth.jwt;

import lombok.Value;

/**
* @author Anton Lamtev
*/
@Value
public class AuthorizedUser {
Long id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package org.accula.api.auth.jwt;

import lombok.RequiredArgsConstructor;
import org.accula.api.auth.jwt.crypto.Jwt;
import org.accula.api.auth.util.CookieRefreshTokenHelper;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.time.Duration;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.springframework.http.HttpStatus.FOUND;
import static org.springframework.http.HttpStatus.OK;
import static org.springframework.http.MediaType.APPLICATION_JSON;

/**
* Produces a response in the form of HTTP-redirect or body in JSON format.
*
* @author Anton Lamtev
* @author Vadim Dyachkov
*/
@RequiredArgsConstructor
public final class JwtAccessTokenResponseProducer {
private static final String RESPONSE_BODY_FORMAT = "{\"accessToken\":\"%s\"}";
private static final byte[] FAILURE_BODY = String.format(RESPONSE_BODY_FORMAT, "").getBytes(UTF_8);

private final Jwt jwt;
private final Duration accessExpiresIn;
private final Duration refreshExpiresIn;
private final CookieRefreshTokenHelper cookieRefreshTokenHelper;
private final String webUrl;

/**
* Produces a response with redirect (i.e. HttpStatus == 302) to the static web-server using the following pattern:
* {@code ${webUrl}/oauth2/redirect?accessToken=${accessToken}}.
*/
public Mono<Void> formSuccessRedirect(final ServerWebExchange exchange, final Long userId, final String refreshToken) {
return Mono.fromRunnable(() -> {
final var response = exchange.getResponse();
final var accessTokenDetails = jwt.generate(userId.toString(), accessExpiresIn);
final var location = URI.create(webUrl + "/oauth2/redirect?accessToken=" + accessTokenDetails.getToken());
response.getHeaders().setLocation(location);
response.setStatusCode(FOUND);
cookieRefreshTokenHelper.set(response.getCookies(), refreshToken, refreshExpiresIn);
});
}

/**
* Produces a response with the access token as a part of body JSON
* (see {@code RESPONSE_BODY_FORMAT}), and refresh token included in cookies
*/
public Mono<Void> formSuccessBody(final ServerWebExchange exchange, final Long userId, final String refreshToken) {
final var response = exchange.getResponse();

return response.writeWith(Mono.fromSupplier(() -> {
final var accessTokenDetails = jwt.generate(userId.toString(), accessExpiresIn);
final var respBody = String.format(RESPONSE_BODY_FORMAT, accessTokenDetails.getToken()).getBytes(UTF_8);

response.setStatusCode(OK);
response.getHeaders().setContentType(APPLICATION_JSON);
response.getHeaders().setContentLength(respBody.length);
cookieRefreshTokenHelper.set(response.getCookies(), refreshToken, refreshExpiresIn);

return response.bufferFactory().wrap(respBody);
}));
}

/**
* Produces a response with an empty access token
*/
public Mono<Void> formFailureBody(final ServerWebExchange exchange) {
final var response = exchange.getResponse();

return response.writeWith(Mono.fromSupplier(() -> {
response.setStatusCode(OK);
response.getHeaders().setContentType(APPLICATION_JSON);
response.getHeaders().setContentLength(FAILURE_BODY.length);

return response.bufferFactory().wrap(FAILURE_BODY);
}));
}
}
12 changes: 12 additions & 0 deletions api/src/main/java/org/accula/api/auth/jwt/JwtAuthFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.accula.api.auth.jwt;

import org.springframework.security.web.server.authentication.AuthenticationWebFilter;

/**
* @author Anton Lamtev
*/
public final class JwtAuthFilter extends AuthenticationWebFilter {
public JwtAuthFilter() {
super(new JwtAuthenticationManager());
}
}
32 changes: 32 additions & 0 deletions api/src/main/java/org/accula/api/auth/jwt/JwtAuthentication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.accula.api.auth.jwt;

import org.jetbrains.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;

import java.util.Collections;

/**
* @author Anton Lamtev
*/
public final class JwtAuthentication extends AbstractAuthenticationToken implements Authentication {
private static final long serialVersionUID = -7833231338530910449L;

private final AuthorizedUser user;

public JwtAuthentication(final AuthorizedUser user) {
super(Collections.emptyList());
this.user = user;
}

@Override
@Nullable
public Object getCredentials() {
return null;
}

@Override
public AuthorizedUser getPrincipal() {
return user;
}
}
Loading

0 comments on commit 2a40c73

Please sign in to comment.