diff --git a/auto-configure/pom.xml b/auto-configure/pom.xml index d1cfc5a..41760c1 100644 --- a/auto-configure/pom.xml +++ b/auto-configure/pom.xml @@ -29,6 +29,12 @@ true + + org.projectlombok + lombok + true + + com.codeyapa core @@ -40,6 +46,12 @@ spring-boot-starter-test test + + + org.projectlombok + lombok + true + \ No newline at end of file diff --git a/auto-configure/src/test/java/com/codeyapa/rest/secured/autoconfiguration/SecurityAutoConfigurationTest.java b/auto-configure/src/test/java/com/codeyapa/rest/secured/autoconfiguration/SecurityAutoConfigurationTest.java index ab3a2a2..eaac56a 100644 --- a/auto-configure/src/test/java/com/codeyapa/rest/secured/autoconfiguration/SecurityAutoConfigurationTest.java +++ b/auto-configure/src/test/java/com/codeyapa/rest/secured/autoconfiguration/SecurityAutoConfigurationTest.java @@ -3,6 +3,7 @@ import org.junit.Test; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.autoconfigure.AutoConfigurations.of; public class SecurityAutoConfigurationTest { @@ -15,7 +16,21 @@ public void shouldCreateAutoConfiguredBeanIfStarterIsEnabled() { .withPropertyValues(properties(true)) .withConfiguration(of(SecurityAutoConfiguration.class)); - this.applicationContextRunner.run(context -> {}); + this.applicationContextRunner.run( + context -> { + }); + } + + @Test + public void shouldNotCreateAutoConfiguredBeanIfStarterIsNotEnabled() { + applicationContextRunner = + new WebApplicationContextRunner() + .withPropertyValues(properties(false)) + .withConfiguration(of(SecurityAutoConfiguration.class)); + + this.applicationContextRunner.run( + context -> { + }); } private String[] properties(boolean enabled) { diff --git a/core/pom.xml b/core/pom.xml index b5d34c9..cf04e98 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -11,6 +11,16 @@ core + + ${project.build.directory}/jacoco.exec + + src/main/java/com/codeyapa/rest/secured/core/domain/* + + + src/main/java/com/codeyapa/rest/secured/core/domain/* + + + org.springframework.boot @@ -24,11 +34,56 @@ provided + + org.projectlombok + lombok + true + + org.springframework.boot spring-boot-starter-test test + + + org.projectlombok + lombok + true + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + org.junit.vintage + junit-vintage-engine + ${junit.jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.platform + junit-platform-suite-api + test + + + org.junit.platform + junit-platform-runner + test + \ No newline at end of file diff --git a/core/src/main/java/com/codeyapa/rest/secured/core/domain/GrantType.java b/core/src/main/java/com/codeyapa/rest/secured/core/domain/GrantType.java new file mode 100644 index 0000000..0720248 --- /dev/null +++ b/core/src/main/java/com/codeyapa/rest/secured/core/domain/GrantType.java @@ -0,0 +1,14 @@ +package com.codeyapa.rest.secured.core.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public enum GrantType { + @JsonProperty("password") + PASSWORD, + + @JsonProperty("refresh_token") + REFRESH_TOKEN, + + @JsonProperty("client_credentials") + CLIENT_CREDENTIALS +} diff --git a/core/src/main/java/com/codeyapa/rest/secured/core/domain/IssueTokenRequest.java b/core/src/main/java/com/codeyapa/rest/secured/core/domain/IssueTokenRequest.java new file mode 100644 index 0000000..1a9fae0 --- /dev/null +++ b/core/src/main/java/com/codeyapa/rest/secured/core/domain/IssueTokenRequest.java @@ -0,0 +1,49 @@ +package com.codeyapa.rest.secured.core.domain; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@AllArgsConstructor +@Getter +@EqualsAndHashCode +public class IssueTokenRequest { + @JsonProperty("client_id") + private String clientId; + + @JsonProperty("grant_type") + private GrantType grantType; + + private String scope; + private String username; + private String password; + private int confidenceLevel; + + @JsonProperty("refresh_token") + private String refreshToken; + + public IssueTokenRequest(String clientId, String username, String password, String scope) { + this.clientId = clientId; + this.grantType = GrantType.PASSWORD; + + this.username = username; + this.password = password; + this.scope = scope; + } + + public IssueTokenRequest(String clientId, String refreshToken) { + this.clientId = clientId; + this.grantType = GrantType.REFRESH_TOKEN; + this.refreshToken = refreshToken; + } + + public IssueTokenRequest(String clientId, String scope, int confidenceLevel) { + this.clientId = clientId; + this.scope = scope; + this.confidenceLevel = confidenceLevel; + this.grantType = GrantType.CLIENT_CREDENTIALS; + } +} diff --git a/core/src/main/java/com/codeyapa/rest/secured/core/domain/IssueTokenResponse.java b/core/src/main/java/com/codeyapa/rest/secured/core/domain/IssueTokenResponse.java new file mode 100644 index 0000000..7c1f03a --- /dev/null +++ b/core/src/main/java/com/codeyapa/rest/secured/core/domain/IssueTokenResponse.java @@ -0,0 +1,35 @@ +package com.codeyapa.rest.secured.core.domain; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class IssueTokenResponse { + @JsonProperty("access_token") + String accessToken; + + @JsonProperty("refresh_token") + String refreshToken; + + @JsonProperty("expires_in") + Integer expiresIn; + + @JsonProperty("token_type") + String tokenType; + + @JsonProperty("scope") + String scope; + + @JsonProperty("confidence_level") + Integer confidenceLevel; +} diff --git a/core/src/main/java/com/codeyapa/rest/secured/core/domain/Token.java b/core/src/main/java/com/codeyapa/rest/secured/core/domain/Token.java new file mode 100644 index 0000000..7412739 --- /dev/null +++ b/core/src/main/java/com/codeyapa/rest/secured/core/domain/Token.java @@ -0,0 +1,34 @@ +package com.codeyapa.rest.secured.core.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Value; + +import java.time.Instant; +import java.util.Set; + +@Builder +@Value +@AllArgsConstructor +@EqualsAndHashCode +public class Token { + String tokenId; + String userId; + String clientId; + Instant validFrom; + Instant expiresAt; + Instant issuedAt; + String issuer; + String scope; + Integer confidenceLevel; + Set audience; + + public boolean isFutureToken() { + return !validFrom.isBefore(Instant.now()); + } + + public boolean isExpired() { + return expiresAt.isBefore(Instant.now()); + } +} diff --git a/core/src/main/java/com/codeyapa/rest/secured/core/domain/ValidateTokenRequest.java b/core/src/main/java/com/codeyapa/rest/secured/core/domain/ValidateTokenRequest.java new file mode 100644 index 0000000..d0862a2 --- /dev/null +++ b/core/src/main/java/com/codeyapa/rest/secured/core/domain/ValidateTokenRequest.java @@ -0,0 +1,14 @@ +package com.codeyapa.rest.secured.core.domain; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Getter +@AllArgsConstructor +public class ValidateTokenRequest { + @JsonProperty("access_token") + private String accessToken; +} diff --git a/core/src/main/java/com/codeyapa/rest/secured/core/domain/ValidateTokenResponse.java b/core/src/main/java/com/codeyapa/rest/secured/core/domain/ValidateTokenResponse.java new file mode 100644 index 0000000..bb8a28e --- /dev/null +++ b/core/src/main/java/com/codeyapa/rest/secured/core/domain/ValidateTokenResponse.java @@ -0,0 +1,85 @@ +package com.codeyapa.rest.secured.core.domain; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; + +import static java.time.Instant.ofEpochSecond; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Slf4j +public class ValidateTokenResponse { + @JsonProperty("active") + private boolean active; + + @JsonProperty("jti") + private String tokenId; + + @JsonProperty("iss") + private String issuer; + + @JsonProperty("sub") + private String subject; + + @JsonProperty("aud") + private Collection audience; + + @JsonProperty("iat") + private Long issuedAt; + + @JsonProperty("nbf") + private Long notBefore; + + @JsonProperty("exp") + private Long expirationTime; + + @JsonProperty("scope") + private String scope; + + @JsonProperty("confidence_level") + private Integer confidenceLevel; + + @JsonProperty("client_id") + private String clientId; + + @JsonProperty("token_type") + private String tokenType; + + public Optional toToken() { + if (!active || subject == null) { + return Optional.empty(); + } + try { + return Optional.of( + Token.builder() + .userId(subject) + .clientId(clientId) + .issuedAt(ofEpochSecond(issuedAt)) + .expiresAt(ofEpochSecond(expirationTime)) + .validFrom(ofEpochSecond(notBefore)) + .confidenceLevel(confidenceLevel) + .audience(audience == null ? Collections.emptySet() : new HashSet<>(audience)) + .scope(scope) + .tokenId(tokenId) + .build()); + } catch (IllegalArgumentException | NullPointerException e) { + log.error("The token could not be parsed as a valid JWT.", e); + return Optional.empty(); + } + } +} diff --git a/core/src/main/java/com/codeyapa/rest/secured/core/properties/SecurityProperties.java b/core/src/main/java/com/codeyapa/rest/secured/core/properties/SecurityProperties.java index bed9e45..e73ab0e 100644 --- a/core/src/main/java/com/codeyapa/rest/secured/core/properties/SecurityProperties.java +++ b/core/src/main/java/com/codeyapa/rest/secured/core/properties/SecurityProperties.java @@ -1,8 +1,12 @@ package com.codeyapa.rest.secured.core.properties; +import lombok.Getter; +import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "rest.security") +@Getter +@Setter public class SecurityProperties { private boolean enabled = true; } diff --git a/pom.xml b/pom.xml index ba4273b..a38773d 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,11 @@ 2.1.6.RELEASE 0.8.4 8.14.1 + 1.16.16 + jacoco + reuseReports + ${project.build.directory}/jacoco.exec + 5.5.1 @@ -40,7 +45,14 @@ nimbus-jose-jwt ${nimbus-jose-jwt.version} + + + org.projectlombok + lombok + ${lombok.version} + +