From aab415f0efa167df53a04704e99470019ab41ae2 Mon Sep 17 00:00:00 2001 From: MediaMarco Date: Fri, 26 Nov 2021 13:59:55 +0100 Subject: [PATCH] Update to Spring Boot 2.6.0, Remove edison-oauth, Fix circular dependency in example-jobs, remove some deprecations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Matthias Geißendörfer Co-authored-by: Marco Geweke --- .../logging/ui/LoggersHtmlEndpoint.java | 38 ++--- .../repository/InMemJobRepositoryTest.java | 8 +- .../dynamo/DynamoJobMetaRepositoryTest.java | 5 +- edison-oauth/README.md | 79 ----------- edison-oauth/build.gradle | 61 -------- .../KeyExchangeJwtAccessTokenConverter.java | 65 --------- .../de/otto/edison/oauth/OAuthPublicKey.java | 107 -------------- .../OAuthPublicKeyInMemoryRepository.java | 34 ----- .../oauth/OAuthPublicKeyRepository.java | 10 -- .../edison/oauth/OAuthPublicKeyStore.java | 101 ------------- .../oauth/ZonedDateTimeDeserializer.java | 15 -- .../GlobalMethodSecurityConfig.java | 18 --- .../OAuthPublicKeyConfiguration.java | 35 ----- .../ResourceServerConfiguration.java | 56 -------- .../RestResponseEntityExceptionHandler.java | 26 ---- .../OAuthPublicKeyInMemoryRepositoryTest.java | 76 ---------- .../edison/oauth/OAuthPublicKeyStoreTest.java | 133 ------------------ .../oauth/ZonedDateTimeDeserializerTest.java | 42 ------ .../ExampleJobMutexGroupConfiguration.java | 15 ++ .../ExampleJobsConfiguration.java | 5 - examples/example-oauth/build.gradle | 14 -- .../de/otto/edison/example/ApiController.java | 24 ---- .../edison/example/ExampleOauthServer.java | 19 --- .../otto/edison/example/OAuthController.java | 46 ------ .../example/configuration/OAuthConfig.java | 17 --- .../edison/example/oauth/OAuthService.java | 61 -------- .../src/main/resources/application.yml | 45 ------ .../src/main/resources/logback-spring.xml | 15 -- .../src/main/resources/version.properties | 6 - .../example/ApiControllerIntegrationTest.java | 133 ------------------ .../otto/edison/example/OAuthTestHelper.java | 47 ------- .../OAuthPublicKeyTestConfiguration.java | 96 ------------- .../src/test/resources/application-test.yml | 9 -- gradle/build.gradle | 2 +- 34 files changed, 30 insertions(+), 1433 deletions(-) delete mode 100644 edison-oauth/README.md delete mode 100644 edison-oauth/build.gradle delete mode 100644 edison-oauth/src/main/java/de/otto/edison/oauth/KeyExchangeJwtAccessTokenConverter.java delete mode 100644 edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKey.java delete mode 100644 edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyInMemoryRepository.java delete mode 100644 edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyRepository.java delete mode 100644 edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyStore.java delete mode 100644 edison-oauth/src/main/java/de/otto/edison/oauth/ZonedDateTimeDeserializer.java delete mode 100644 edison-oauth/src/main/java/de/otto/edison/oauth/configuration/GlobalMethodSecurityConfig.java delete mode 100644 edison-oauth/src/main/java/de/otto/edison/oauth/configuration/OAuthPublicKeyConfiguration.java delete mode 100644 edison-oauth/src/main/java/de/otto/edison/oauth/configuration/ResourceServerConfiguration.java delete mode 100644 edison-oauth/src/main/java/de/otto/edison/oauth/configuration/RestResponseEntityExceptionHandler.java delete mode 100644 edison-oauth/src/test/java/de/otto/edison/oauth/OAuthPublicKeyInMemoryRepositoryTest.java delete mode 100644 edison-oauth/src/test/java/de/otto/edison/oauth/OAuthPublicKeyStoreTest.java delete mode 100644 edison-oauth/src/test/java/de/otto/edison/oauth/ZonedDateTimeDeserializerTest.java create mode 100644 examples/example-jobs/src/main/java/de/otto/edison/example/configuration/ExampleJobMutexGroupConfiguration.java delete mode 100644 examples/example-oauth/build.gradle delete mode 100644 examples/example-oauth/src/main/java/de/otto/edison/example/ApiController.java delete mode 100644 examples/example-oauth/src/main/java/de/otto/edison/example/ExampleOauthServer.java delete mode 100644 examples/example-oauth/src/main/java/de/otto/edison/example/OAuthController.java delete mode 100644 examples/example-oauth/src/main/java/de/otto/edison/example/configuration/OAuthConfig.java delete mode 100644 examples/example-oauth/src/main/java/de/otto/edison/example/oauth/OAuthService.java delete mode 100644 examples/example-oauth/src/main/resources/application.yml delete mode 100644 examples/example-oauth/src/main/resources/logback-spring.xml delete mode 100644 examples/example-oauth/src/main/resources/version.properties delete mode 100644 examples/example-oauth/src/test/java/de/otto/edison/example/ApiControllerIntegrationTest.java delete mode 100644 examples/example-oauth/src/test/java/de/otto/edison/example/OAuthTestHelper.java delete mode 100644 examples/example-oauth/src/test/java/de/otto/edison/example/configuration/OAuthPublicKeyTestConfiguration.java delete mode 100644 examples/example-oauth/src/test/resources/application-test.yml diff --git a/edison-core/src/main/java/de/otto/edison/logging/ui/LoggersHtmlEndpoint.java b/edison-core/src/main/java/de/otto/edison/logging/ui/LoggersHtmlEndpoint.java index 026519ea0..c81b3b0e0 100644 --- a/edison-core/src/main/java/de/otto/edison/logging/ui/LoggersHtmlEndpoint.java +++ b/edison-core/src/main/java/de/otto/edison/logging/ui/LoggersHtmlEndpoint.java @@ -2,36 +2,23 @@ import de.otto.edison.configuration.EdisonApplicationProperties; import de.otto.edison.navigation.NavBar; -import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.logging.LoggersEndpoint; import org.springframework.boot.actuate.logging.LoggersEndpoint.LoggerLevels; import org.springframework.boot.logging.LogLevel; import org.springframework.http.HttpEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.view.RedirectView; import javax.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.StringJoiner; -import java.util.StringTokenizer; +import java.util.*; import static de.otto.edison.navigation.NavBarItem.navBarItem; import static de.otto.edison.util.UrlHelper.baseUriOf; import static java.util.stream.Collectors.toList; import static org.springframework.boot.logging.LogLevel.valueOf; -import static org.springframework.http.MediaType.ALL_VALUE; -import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import static org.springframework.http.MediaType.TEXT_HTML_VALUE; +import static org.springframework.http.MediaType.*; import static org.springframework.http.ResponseEntity.notFound; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; @@ -61,7 +48,8 @@ public LoggersHtmlEndpoint(final LoggersEndpoint loggersEndpoint, value = "${edison.application.management.base-path:/internal}/loggers", produces = { TEXT_HTML_VALUE, - ALL_VALUE}, + ALL_VALUE + }, method = GET) public ModelAndView get(final HttpServletRequest request) { return new ModelAndView("loggers", new HashMap() {{ @@ -72,9 +60,7 @@ public ModelAndView get(final HttpServletRequest request) { @RequestMapping( value = "${edison.application.management.base-path:/internal}/loggers", - produces = { - ActuatorMediaType.V2_JSON, - APPLICATION_JSON_VALUE}, + produces = APPLICATION_JSON_VALUE, method = GET) @ResponseBody public Object get() { @@ -84,9 +70,7 @@ public Object get() { @RequestMapping( value = "${edison.application.management.base-path:/internal}/loggers/{name:.*}", - produces = { - ActuatorMediaType.V2_JSON, - APPLICATION_JSON_VALUE}, + produces = APPLICATION_JSON_VALUE, method = GET) @ResponseBody public Object get(@PathVariable String name) { @@ -109,12 +93,8 @@ public RedirectView post(@ModelAttribute("name") String name, @RequestMapping( value = "${edison.application.management.base-path:/internal}/loggers/{name:.*}", - consumes = { - ActuatorMediaType.V2_JSON, - APPLICATION_JSON_VALUE}, - produces = { - ActuatorMediaType.V2_JSON, - APPLICATION_JSON_VALUE}, + consumes = APPLICATION_JSON_VALUE, + produces = APPLICATION_JSON_VALUE, method = POST) @ResponseBody public Object post(@PathVariable String name, diff --git a/edison-jobs/src/test/java/de/otto/edison/jobs/repository/InMemJobRepositoryTest.java b/edison-jobs/src/test/java/de/otto/edison/jobs/repository/InMemJobRepositoryTest.java index 3069ae615..ae31d26b7 100644 --- a/edison-jobs/src/test/java/de/otto/edison/jobs/repository/InMemJobRepositoryTest.java +++ b/edison-jobs/src/test/java/de/otto/edison/jobs/repository/InMemJobRepositoryTest.java @@ -31,10 +31,8 @@ import static java.time.ZoneId.systemDefault; import static java.util.Arrays.asList; import static java.util.UUID.randomUUID; -import static org.assertj.core.util.Lists.emptyList; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; public class InMemJobRepositoryTest { @@ -46,7 +44,7 @@ public void setUp() throws Exception { } - private Clock clock = systemDefaultZone(); + private final Clock clock = systemDefaultZone(); @Test public void shouldFindJobInfoByUri() { @@ -308,7 +306,7 @@ public void shouldClearJobInfos() throws Exception { repository.deleteAll(); //Then - assertThat(repository.findAll(), is(emptyList())); + assertThat(repository.findAll(), is(empty())); } private JobInfo jobInfo(final String jobId, final String type) { diff --git a/edison-jobs/src/test/java/de/otto/edison/jobs/repository/dynamo/DynamoJobMetaRepositoryTest.java b/edison-jobs/src/test/java/de/otto/edison/jobs/repository/dynamo/DynamoJobMetaRepositoryTest.java index 1b867008b..17fbd7156 100644 --- a/edison-jobs/src/test/java/de/otto/edison/jobs/repository/dynamo/DynamoJobMetaRepositoryTest.java +++ b/edison-jobs/src/test/java/de/otto/edison/jobs/repository/dynamo/DynamoJobMetaRepositoryTest.java @@ -1,7 +1,6 @@ package de.otto.edison.jobs.repository.dynamo; import de.otto.edison.jobs.domain.JobMeta; -import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,8 +21,8 @@ import static de.otto.edison.jobs.repository.dynamo.DynamoJobMetaRepository.JOB_TYPE_KEY; import static de.otto.edison.jobs.repository.dynamo.DynamoJobMetaRepository.KEY_DISABLED; import static java.util.Collections.emptySet; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertThat; @Testcontainers public class DynamoJobMetaRepositoryTest { @@ -294,6 +293,6 @@ void shouldDeleteAll() { dynamoJobMetaRepository.deleteAll(); //then - MatcherAssert.assertThat(dynamoJobMetaRepository.findAllJobTypes(), is(emptySet())); + assertThat(dynamoJobMetaRepository.findAllJobTypes(), is(emptySet())); } } \ No newline at end of file diff --git a/edison-oauth/README.md b/edison-oauth/README.md deleted file mode 100644 index 5e4fd9b29..000000000 --- a/edison-oauth/README.md +++ /dev/null @@ -1,79 +0,0 @@ -## Edison OAuth Capabilities - -Basic set of functionality to use OAuth for securing APIs - -## OAuth Public Key Synchronisation - -### Description - -This can be used to retrieve a list of public keys from an authorization server. These public keys -can be used to validate the issuer of OAuth Token when receiving requests. - -### Usage - -Enable by setting the following properties: - -```properties -edison.oauth.public-key.enabled=true -edison.oauth.public-key.url=https://base.url/public/key/path -edison.oauth.public-key.interval=1800000 -``` - -(`interval` defines the time in milliseconds between retrievals of public keys) - -The retrieved Data has to be in the following format: - -```json -[ - { - publicKey: "-----BEGIN PUBLIC KEY----- publicKeyData -----END PUBLIC KEY----- ", - publicKeyFingerprint: "fingerprintOfThePublicKey", - validFrom: "ZonedDateTime formatted Date", - validUntil: "ZonedDateTime formatted Date OR null" - } -] -``` - -A key is valid if one of the following conditions is true: - -`validFrom < now < validUntil` - -or - -`valid from < now && validUntil == null` - -All valid keys are kept and can be validated against. - -## OAuth authorisation for Controller - -### Description - -An OAuth2 Security Filter is added and can be used to verify users based on their scope inside -their `Authorization`-Header. - -### Usage - -The configuration of the OAuthSecurity Beans is done automatically when adding the module `edison-oauth`. -Configure it by setting the following properties: - -```properties -edison.oauth.authorization.resource.patterns=["/secured/path","/another/secured/path/**"] -edison.oauth.jwt.audience=https://api.otto.de/api-authorization -``` - -(`patterns` defaults to `/oauth/**`) - -Afterwards, you can use an annotation at the controller method that checks the request -for a certain `scope` inside the JWT Data: - -```java - @RequestMapping(method = GET, value = "/secured/path", produces = MediaType.APPLICATION_JSON) - @ResponseBody - @PreAuthorize("#oauth2.hasScope('some.oauth.scope')") - public List getObjects() { - ... - } -``` - -If the user is not authorized, he'll retrieve a HttpStatus of `401`. See the Spring documentation on -OAuth for more details: https://spring.io/projects/spring-security-oauth diff --git a/edison-oauth/build.gradle b/edison-oauth/build.gradle deleted file mode 100644 index 711e5d3d1..000000000 --- a/edison-oauth/build.gradle +++ /dev/null @@ -1,61 +0,0 @@ -dependencies { - compile libraries.spring_boot_autoconfigure - compile libraries.spring_boot_starter_web - compile libraries.spring_security - compile libraries.spring_security_web - compile libraries.spring_security_oauth - compile libraries.spring_security_jwt - compile java_xml - implementation libraries.hibernate_validator - - testCompile project(":edison-testsupport") -} - -artifacts { - archives jar - archives javadocJar - archives sourcesJar -} - -uploadArchives { - repositories { - mavenDeployer { - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - - repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { - authentication(userName: sonatypeUsername, password: sonatypePassword) - } - snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { - authentication(userName: sonatypeUsername, password: sonatypePassword) - } - - pom.project { - name 'edison-oauth' - packaging 'jar' - description 'OAuth Capabilities of Edison-AWS microservices.' - url 'http://github.com/otto-de/edison-microservice' - - scm { - url 'scm:git@github.com:otto-de/edison-microservice.git' - connection 'scm:git@github.com:otto-de/edison-microservice.git' - developerConnection 'scm:git@github.com:otto-de/edison-microservice.git' - } - - licenses { - license { - name 'The Apache Software License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - distribution 'repo' - } - } - - developers { - developer { - id 'gsteinacker' - name 'Guido Steinacker' - } - } - } - } - } -} diff --git a/edison-oauth/src/main/java/de/otto/edison/oauth/KeyExchangeJwtAccessTokenConverter.java b/edison-oauth/src/main/java/de/otto/edison/oauth/KeyExchangeJwtAccessTokenConverter.java deleted file mode 100644 index 21892f960..000000000 --- a/edison-oauth/src/main/java/de/otto/edison/oauth/KeyExchangeJwtAccessTokenConverter.java +++ /dev/null @@ -1,65 +0,0 @@ -package de.otto.edison.oauth; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.security.jwt.Jwt; -import org.springframework.security.jwt.JwtHelper; -import org.springframework.security.jwt.crypto.sign.RsaVerifier; -import org.springframework.security.oauth2.common.util.JsonParser; -import org.springframework.security.oauth2.common.util.JsonParserFactory; -import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; -import org.springframework.stereotype.Component; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -@Component -//@ConditionalOnExpression("${edison.oauth.public-key.enabled:false}") -public class KeyExchangeJwtAccessTokenConverter extends JwtAccessTokenConverter { - - private static final Logger LOG = LoggerFactory.getLogger(KeyExchangeJwtAccessTokenConverter.class); - private static final JsonParser objectMapper = JsonParserFactory.create(); - private final OAuthPublicKeyStore oAuthPublicKeyStore; - - @Autowired - public KeyExchangeJwtAccessTokenConverter(final OAuthPublicKeyStore oAuthPublicKeyStore) { - this.oAuthPublicKeyStore = oAuthPublicKeyStore; - } - - @Override - protected Map decode(final String token) { - final List currentPublicKeys = oAuthPublicKeyStore.getActivePublicKeys(); - - for (final OAuthPublicKey publicKey : currentPublicKeys) { - try { - return decodeJwtMap(token, publicKey); - } catch (final Exception e) { - LOG.debug(String.format("Unable to verify JWT token with public key: %s", publicKey.getPublicKeyFingerprint())); - } - } - return Collections.emptyMap(); - } - - private Map decodeJwtMap(final String token, final OAuthPublicKey keyExchangePublicKey) { - final RsaVerifier rsaVerifier = new RsaVerifier(keyExchangePublicKey.getPublicKey()); - final Jwt jwt = JwtHelper.decodeAndVerify(token, rsaVerifier); - - final String content = jwt.getClaims(); - - final Map map = objectMapper.parseMap(content); - if (map.containsKey(EXP) && map.get(EXP) instanceof Integer) { - final Integer intValue = (Integer) map.get(EXP); - map.put(EXP, Long.valueOf(intValue)); - } - return map; - } - - @Override - public void afterPropertiesSet() { - // override with empty method because we do not use any signer and to suppress unnecessary warning - } -} - diff --git a/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKey.java b/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKey.java deleted file mode 100644 index 9b1451320..000000000 --- a/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKey.java +++ /dev/null @@ -1,107 +0,0 @@ -package de.otto.edison.oauth; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.springframework.validation.annotation.Validated; - -import javax.validation.constraints.NotNull; -import java.time.ZonedDateTime; -import java.util.Objects; - -@Validated -@JsonDeserialize(builder = OAuthPublicKey.Builder.class) -public class OAuthPublicKey { - - private final String publicKey; - private final String publicKeyFingerprint; - private final ZonedDateTime validFrom; - private final ZonedDateTime validUntil; - - private OAuthPublicKey(final Builder builder) { - this.publicKey = builder.publicKey; - this.publicKeyFingerprint = builder.publicKeyFingerprint; - this.validFrom = builder.validFrom; - this.validUntil = builder.validUntil; - } - - public String getPublicKey() { - return publicKey; - } - - public String getPublicKeyFingerprint() { - return publicKeyFingerprint; - } - - public ZonedDateTime getValidFrom() { - return validFrom; - } - - public ZonedDateTime getValidUntil() { - return validUntil; - } - - public static Builder oAuthPublicKeyBuilder() { - return new Builder(); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final OAuthPublicKey that = (OAuthPublicKey) o; - return Objects.equals(publicKey, that.publicKey) && - Objects.equals(publicKeyFingerprint, that.publicKeyFingerprint) && - Objects.equals(validFrom, that.validFrom) && - Objects.equals(validUntil, that.validUntil); - } - - @Override - public int hashCode() { - return Objects.hash(publicKey, publicKeyFingerprint, validFrom, validUntil); - } - - @Override - public String toString() { - return "OAuthPublicKey{" + - "publicKey='" + publicKey + '\'' + - ", publicKeyFingerprint='" + publicKeyFingerprint + '\'' + - ", validFrom=" + validFrom + - ", validUntil=" + validUntil + - '}'; - } - - public static class Builder { - @NotNull - private String publicKey; - - @NotNull - private String publicKeyFingerprint; - - private ZonedDateTime validFrom; - - private ZonedDateTime validUntil; - - public Builder withPublicKey(final String publicKey) { - this.publicKey = publicKey; - return this; - } - - public Builder withPublicKeyFingerprint(final String publicKeyFingerprint) { - this.publicKeyFingerprint = publicKeyFingerprint; - return this; - } - - public Builder withValidFrom(final ZonedDateTime validFrom) { - this.validFrom = validFrom; - return this; - } - - public Builder withValidUntil(final ZonedDateTime validUntil) { - this.validUntil = validUntil; - return this; - } - - public OAuthPublicKey build() { - return new OAuthPublicKey(this); - } - } -} diff --git a/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyInMemoryRepository.java b/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyInMemoryRepository.java deleted file mode 100644 index e969389f0..000000000 --- a/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyInMemoryRepository.java +++ /dev/null @@ -1,34 +0,0 @@ -package de.otto.edison.oauth; - -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; - -public class OAuthPublicKeyInMemoryRepository implements OAuthPublicKeyRepository { - - private final List activePublicKeys = new CopyOnWriteArrayList<>(); - - @Override - public void refreshPublicKeys(final List publicKeys) { - activePublicKeys.addAll(publicKeys - .stream() - .filter(this::isValid) - .filter(k -> !activePublicKeys.contains(k)) - .collect(Collectors.toList()) - ); - } - - @Override - public List retrieveActivePublicKeys() { - return activePublicKeys.stream().filter(this::isValid).collect(Collectors.toList()); - } - - private boolean isValid(final OAuthPublicKey publicKey) { - final ZonedDateTime now = ZonedDateTime.now(); - - return now.isAfter(publicKey.getValidFrom()) && - (Objects.isNull(publicKey.getValidUntil()) || now.isBefore(publicKey.getValidUntil())); - } -} diff --git a/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyRepository.java b/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyRepository.java deleted file mode 100644 index 4a5cc5aea..000000000 --- a/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package de.otto.edison.oauth; - -import java.util.List; - -public interface OAuthPublicKeyRepository { - - void refreshPublicKeys(List publicKeys); - - List retrieveActivePublicKeys(); -} diff --git a/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyStore.java b/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyStore.java deleted file mode 100644 index 3ace6ac7e..000000000 --- a/edison-oauth/src/main/java/de/otto/edison/oauth/OAuthPublicKeyStore.java +++ /dev/null @@ -1,101 +0,0 @@ -package de.otto.edison.oauth; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.type.TypeFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.http.HttpStatus; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Duration; -import java.time.ZonedDateTime; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -@Component -@ConditionalOnExpression("${edison.oauth.public-key.enabled:false} && ${edison.oauth.public-key.interval:0}>0") -public class OAuthPublicKeyStore { - private static final Logger LOG = LoggerFactory.getLogger(OAuthPublicKeyStore.class); - private final ObjectMapper objectMapper; - private final String publicKeyUrl; - private final HttpClient httpClient; - private final OAuthPublicKeyRepository oAuthPublicKeyRepository; - private final CountDownLatch publicKeysRetrievedCountDownLatch = new CountDownLatch(1); - - @Autowired - public OAuthPublicKeyStore(@Value("${edison.oauth.public-key.url}") final String publicKeyUrl, - final HttpClient asyncHttpClient, - final OAuthPublicKeyRepository oAuthPublicKeyRepository) { - this.publicKeyUrl = publicKeyUrl; - this.oAuthPublicKeyRepository = oAuthPublicKeyRepository; - this.httpClient = asyncHttpClient; - - this.objectMapper = createObjectMapper(); - } - - public List getActivePublicKeys() { - try { - if (publicKeysRetrievedCountDownLatch.await(10, TimeUnit.SECONDS)) { - return oAuthPublicKeyRepository.retrieveActivePublicKeys(); - } else { - throw new RuntimeException("Timeout while waiting that public keys got fetched"); - } - } catch (InterruptedException e) { - throw new RuntimeException("Got interrupted while waiting that public keys got fetched"); - } - } - - @Scheduled(fixedDelayString = "${edison.oauth.public-key.interval}") - public void refreshPublicKeys() { - LOG.info("Start refreshing public keys"); - List oAuthPublicKeys = fetchOAuthPublicKeysFromServer(); - oAuthPublicKeyRepository.refreshPublicKeys(oAuthPublicKeys); - publicKeysRetrievedCountDownLatch.countDown(); - LOG.info("Done refreshing public keys"); - - } - - List fetchOAuthPublicKeysFromServer() { - try { - final HttpResponse response = httpClient - .send(HttpRequest.newBuilder() - .GET() - .uri(URI.create(publicKeyUrl)) - .timeout(Duration.ofMillis(5000)) - .build(), HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() == HttpStatus.OK.value()) { - return objectMapper.readValue(response.body().toString(), TypeFactory.defaultInstance().constructCollectionType(List.class, OAuthPublicKey.class)); - } else { - LOG.warn("Unable to retrieve list of public keys. Got status code {}", response.statusCode()); - } - } catch (IOException e) { - LOG.error("Unable to retrieve list of public keys. ", e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - return Collections.emptyList(); - } - - private ObjectMapper createObjectMapper() { - ObjectMapper newObjectMapper = new ObjectMapper(); - SimpleModule module = new SimpleModule(); - module.addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializer()); - newObjectMapper.registerModule(module); - return newObjectMapper; - } - -} - diff --git a/edison-oauth/src/main/java/de/otto/edison/oauth/ZonedDateTimeDeserializer.java b/edison-oauth/src/main/java/de/otto/edison/oauth/ZonedDateTimeDeserializer.java deleted file mode 100644 index d7db0d2ee..000000000 --- a/edison-oauth/src/main/java/de/otto/edison/oauth/ZonedDateTimeDeserializer.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.otto.edison.oauth; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; - -import java.io.IOException; -import java.time.ZonedDateTime; - -public class ZonedDateTimeDeserializer extends JsonDeserializer { - @Override - public ZonedDateTime deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException { - return ZonedDateTime.parse(p.getText()); - } -} diff --git a/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/GlobalMethodSecurityConfig.java b/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/GlobalMethodSecurityConfig.java deleted file mode 100644 index 82ce4640a..000000000 --- a/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/GlobalMethodSecurityConfig.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.otto.edison.oauth.configuration; - -import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; -import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler; - -@Configuration -@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true, securedEnabled = true) -public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration { - - @Override - protected MethodSecurityExpressionHandler createExpressionHandler() { - return new OAuth2MethodSecurityExpressionHandler(); - } - -} diff --git a/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/OAuthPublicKeyConfiguration.java b/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/OAuthPublicKeyConfiguration.java deleted file mode 100644 index bf3695070..000000000 --- a/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/OAuthPublicKeyConfiguration.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.otto.edison.oauth.configuration; - -import de.otto.edison.oauth.OAuthPublicKeyInMemoryRepository; -import de.otto.edison.oauth.OAuthPublicKeyRepository; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; - -import java.net.http.HttpClient; - -@Configuration -public class OAuthPublicKeyConfiguration extends WebSecurityConfigurerAdapter { - - @Bean - @Override - @ConditionalOnMissingBean(AuthenticationManager.class) - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } - - @Bean - @ConditionalOnMissingBean(OAuthPublicKeyRepository.class) - public OAuthPublicKeyRepository inMemoryRepository() { - return new OAuthPublicKeyInMemoryRepository(); - } - - @Bean - @ConditionalOnMissingBean(HttpClient.class) - public HttpClient asyncHttpClient() { - return HttpClient.newHttpClient(); - } - -} diff --git a/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/ResourceServerConfiguration.java b/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/ResourceServerConfiguration.java deleted file mode 100644 index b2db8bad8..000000000 --- a/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/ResourceServerConfiguration.java +++ /dev/null @@ -1,56 +0,0 @@ -package de.otto.edison.oauth.configuration; - -import de.otto.edison.oauth.KeyExchangeJwtAccessTokenConverter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; -import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; -import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; -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 javax.annotation.Resource; - -@Configuration -@EnableResourceServer -public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { - - @Resource - private KeyExchangeJwtAccessTokenConverter keyExchangeJwtAccessTokenConverter; - - @Value("${edison.oauth.jwt.audience}") - private String audience; - - @Value("${edison.oauth.authorization.resource.patterns:/oauth2/**}") - private String[] resourceServerUrlPatterns; - - @Override - public void configure(final ResourceServerSecurityConfigurer config) { - config.tokenServices(tokenServices()).resourceId(audience); - } - - @Override - public void configure(final HttpSecurity http) throws Exception { - http - .headers().disable() - .csrf().disable() - .authorizeRequests().antMatchers(resourceServerUrlPatterns).authenticated(); - } - - private JwtAccessTokenConverter accessTokenConverter() { - return keyExchangeJwtAccessTokenConverter; - } - - private TokenStore tokenStore() { - return new JwtTokenStore(accessTokenConverter()); - } - - private DefaultTokenServices tokenServices() { - final DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); - defaultTokenServices.setTokenStore(tokenStore()); - return defaultTokenServices; - } -} diff --git a/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/RestResponseEntityExceptionHandler.java b/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/RestResponseEntityExceptionHandler.java deleted file mode 100644 index af8a8aa10..000000000 --- a/edison-oauth/src/main/java/de/otto/edison/oauth/configuration/RestResponseEntityExceptionHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.otto.edison.oauth.configuration; - -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; -import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; - -import java.util.HashMap; -import java.util.Map; - -@ControllerAdvice -public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler { - - @ExceptionHandler({AccessDeniedException.class}) - public ResponseEntity handleAccessDeniedException(final Exception ex, final WebRequest request) { - final Map message = new HashMap<>(); - message.put("error", HttpStatus.FORBIDDEN.getReasonPhrase().toLowerCase()); - message.put("error_description", ex.getMessage()); - - return new ResponseEntity<>(message, HttpStatus.FORBIDDEN); - } -} - diff --git a/edison-oauth/src/test/java/de/otto/edison/oauth/OAuthPublicKeyInMemoryRepositoryTest.java b/edison-oauth/src/test/java/de/otto/edison/oauth/OAuthPublicKeyInMemoryRepositoryTest.java deleted file mode 100644 index 4f5f4d459..000000000 --- a/edison-oauth/src/test/java/de/otto/edison/oauth/OAuthPublicKeyInMemoryRepositoryTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package de.otto.edison.oauth; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static de.otto.edison.oauth.OAuthPublicKey.oAuthPublicKeyBuilder; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -class OAuthPublicKeyInMemoryRepositoryTest { - - - private OAuthPublicKeyInMemoryRepository inMemoryRepository; - - @BeforeEach - void setUp() { - inMemoryRepository = new OAuthPublicKeyInMemoryRepository(); - } - - @Test - void shouldUpdateCachedListWithValidKeys() { - // given - final ZonedDateTime now = ZonedDateTime.now(); - final OAuthPublicKey publicKeyOne = oAuthPublicKeyBuilder() - .withPublicKey("publicKeyOne") - .withPublicKeyFingerprint("fingerPrintOne") - .withValidFrom(now.minusDays(1)) - .withValidUntil(now.plusDays(1)) - .build(); - final OAuthPublicKey publicKeyTwo = oAuthPublicKeyBuilder() - .withPublicKey("publicKeyTwo") - .withPublicKeyFingerprint("fingerPrintTwo") - .withValidFrom(now.minusDays(2)) - .withValidUntil(now.plusDays(2)) - .build(); - final List validPublicKeys = Arrays.asList( - publicKeyOne, - publicKeyTwo - ); - - // when - inMemoryRepository.refreshPublicKeys(validPublicKeys); - - // then - assertThat(inMemoryRepository.retrieveActivePublicKeys(), is(validPublicKeys)); - } - - - @Test - void shouldNotUpdateCachedListWithExistingKey() { - // given - final ZonedDateTime now = ZonedDateTime.now(); - final OAuthPublicKey publicKeyOne = oAuthPublicKeyBuilder() - .withPublicKey("publicKeyOne") - .withPublicKeyFingerprint("fingerPrintOne") - .withValidFrom(now.minusDays(1)) - .withValidUntil(now.plusDays(1)) - .build(); - final List validPublicKeys = Collections.singletonList(publicKeyOne); - - inMemoryRepository.refreshPublicKeys(validPublicKeys); - final List activeKeys = inMemoryRepository.retrieveActivePublicKeys(); - - // when - inMemoryRepository.refreshPublicKeys(validPublicKeys); - - // then - assertThat(inMemoryRepository.retrieveActivePublicKeys(), is(activeKeys)); - } -} \ No newline at end of file diff --git a/edison-oauth/src/test/java/de/otto/edison/oauth/OAuthPublicKeyStoreTest.java b/edison-oauth/src/test/java/de/otto/edison/oauth/OAuthPublicKeyStoreTest.java deleted file mode 100644 index bfd5b1ec7..000000000 --- a/edison-oauth/src/test/java/de/otto/edison/oauth/OAuthPublicKeyStoreTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package de.otto.edison.oauth; - -import org.hamcrest.Matchers; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; - -import java.io.IOException; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.ZonedDateTime; -import java.util.List; - -import static de.otto.edison.oauth.OAuthPublicKey.oAuthPublicKeyBuilder; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; - -class OAuthPublicKeyStoreTest { - - private static final String PUBLIC_KEY_URL = String.format("http://localhost:%d", 8080); - - private static final ZonedDateTime now = ZonedDateTime.now(); - private static final ZonedDateTime oneDayAgo = now.minusDays(1); - private static final ZonedDateTime oneDayAhead = now.plusDays(1); - private static final ZonedDateTime twoDaysAgo = now.minusDays(2); - - private OAuthPublicKeyStore keyStore; - - @Mock - private HttpClient asyncHttpClient; - - private OAuthPublicKeyRepository oAuthPublicKeyRepository = new OAuthPublicKeyInMemoryRepository(); - - @Mock - private HttpResponse response; - - @BeforeEach - void setUp() throws InterruptedException, IOException { - openMocks(this); - keyStore = new OAuthPublicKeyStore(PUBLIC_KEY_URL, asyncHttpClient, oAuthPublicKeyRepository); - withPublicKeyResponse("[\n" + - "{\n" + - "\"publicKey\": \"publicKeyOne\",\n" + - "\"publicKeyFingerprint\": \"fingerPrintOne\",\n" + - "\"validFrom\": \"" + oneDayAgo.toString() + "\",\n" + - "\"validUntil\": \"" + oneDayAhead.toString() + "\"\n" + - "},\n" + - "{\n" + - "\"publicKey\": \"publicKeyTwo\",\n" + - "\"publicKeyFingerprint\": \"fingerPrintTwo\",\n" + - "\"validFrom\": \"" + twoDaysAgo.toString() + "\",\n" + - "\"validUntil\": null\n" + - "}\n" + - "]"); - - } - - @Test - void shouldReturnInitiallyFetchedPublicKeys() { - //given - keyStore.refreshPublicKeys(); - - //when - List activePublicKeys = keyStore.getActivePublicKeys(); - - //then - final OAuthPublicKey publicKeyOne = oAuthPublicKeyBuilder() - .withPublicKey("publicKeyOne") - .withPublicKeyFingerprint("fingerPrintOne") - .withValidFrom(oneDayAgo) - .withValidUntil(oneDayAhead) - .build(); - final OAuthPublicKey publicKeyTwo = oAuthPublicKeyBuilder() - .withPublicKey("publicKeyTwo") - .withPublicKeyFingerprint("fingerPrintTwo") - .withValidFrom(twoDaysAgo) - .withValidUntil(null) - .build(); - - assertThat(activePublicKeys, Matchers.contains(publicKeyOne, publicKeyTwo)); - } - - @Test - void shouldAddKeysToInitiallyFetchedOned() throws InterruptedException, IOException { - //given - keyStore.refreshPublicKeys(); - - //when - withPublicKeyResponse("[\n" + - " {\n" + - " \"publicKey\": \"publicKeyThree\",\n" + - " \"publicKeyFingerprint\": \"fingerPrintThree\",\n" + - " \"validFrom\": \"" + twoDaysAgo.toString() + "\",\n" + - " \"validUntil\": \"" + oneDayAhead.toString() + "\"\n" + - " }\n" + - "]"); - keyStore.refreshPublicKeys(); - - //then - List activePublicKeys = keyStore.getActivePublicKeys(); - - - final OAuthPublicKey publicKeyOne = oAuthPublicKeyBuilder() - .withPublicKey("publicKeyOne") - .withPublicKeyFingerprint("fingerPrintOne") - .withValidFrom(oneDayAgo) - .withValidUntil(oneDayAhead) - .build(); - final OAuthPublicKey publicKeyTwo = oAuthPublicKeyBuilder() - .withPublicKey("publicKeyTwo") - .withPublicKeyFingerprint("fingerPrintTwo") - .withValidFrom(twoDaysAgo) - .withValidUntil(null) - .build(); - final OAuthPublicKey publicKeyThree = oAuthPublicKeyBuilder() - .withPublicKey("publicKeyThree") - .withPublicKeyFingerprint("fingerPrintThree") - .withValidFrom(twoDaysAgo) - .withValidUntil(oneDayAhead) - .build(); - - assertThat(activePublicKeys, Matchers.containsInAnyOrder(publicKeyOne, publicKeyTwo, publicKeyThree)); - } - - private void withPublicKeyResponse(String publicKeyData) throws InterruptedException, IOException { - when(asyncHttpClient.send(any(HttpRequest.class), any())).thenReturn(response); - when(response.statusCode()).thenReturn(200); - when(response.body()).thenReturn(publicKeyData); - } -} diff --git a/edison-oauth/src/test/java/de/otto/edison/oauth/ZonedDateTimeDeserializerTest.java b/edison-oauth/src/test/java/de/otto/edison/oauth/ZonedDateTimeDeserializerTest.java deleted file mode 100644 index 4b8979ff4..000000000 --- a/edison-oauth/src/test/java/de/otto/edison/oauth/ZonedDateTimeDeserializerTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package de.otto.edison.oauth; - -import com.fasterxml.jackson.core.JsonParser; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; - -import java.io.IOException; -import java.time.ZoneId; -import java.time.ZonedDateTime; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; - -public class ZonedDateTimeDeserializerTest { - - @Mock - private JsonParser jsonParser; - - private ZonedDateTimeDeserializer zonedDateTimeDeserializer; - - @BeforeEach - public void setUp() { - openMocks(this); - zonedDateTimeDeserializer = new ZonedDateTimeDeserializer(); - } - - @Test - public void shouldDeserializeZonedDateTime() throws IOException { - // given - when(jsonParser.getText()).thenReturn("2017-10-19T16:10:00.000+02:00[Europe/Berlin]"); - final ZonedDateTime expectedDateTime = ZonedDateTime.of(2017,10,19,16,10,0,0, ZoneId.of("Europe/Berlin")); - - // when - final ZonedDateTime deserialized = zonedDateTimeDeserializer.deserialize(jsonParser, null); - - // then - assertThat(deserialized, is(expectedDateTime)); - } -} diff --git a/examples/example-jobs/src/main/java/de/otto/edison/example/configuration/ExampleJobMutexGroupConfiguration.java b/examples/example-jobs/src/main/java/de/otto/edison/example/configuration/ExampleJobMutexGroupConfiguration.java new file mode 100644 index 000000000..41379a33d --- /dev/null +++ b/examples/example-jobs/src/main/java/de/otto/edison/example/configuration/ExampleJobMutexGroupConfiguration.java @@ -0,0 +1,15 @@ +package de.otto.edison.example.configuration; + +import de.otto.edison.jobs.service.JobMutexGroup; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ExampleJobMutexGroupConfiguration { + + @Bean + public JobMutexGroup mutualExclusion() { + return new JobMutexGroup("barFizzle", "Bar", "Fizzle"); + } + +} diff --git a/examples/example-jobs/src/main/java/de/otto/edison/example/configuration/ExampleJobsConfiguration.java b/examples/example-jobs/src/main/java/de/otto/edison/example/configuration/ExampleJobsConfiguration.java index ad3637773..338390c1b 100644 --- a/examples/example-jobs/src/main/java/de/otto/edison/example/configuration/ExampleJobsConfiguration.java +++ b/examples/example-jobs/src/main/java/de/otto/edison/example/configuration/ExampleJobsConfiguration.java @@ -35,9 +35,4 @@ public KeepLastJobs keepLast10FooJobsCleanupStrategy(final JobRepository jobRepo public StopDeadJobs stopDeadJobsStrategy() { return new StopDeadJobs(jobService, 60); } - - @Bean - public JobMutexGroup mutualExclusion() { - return new JobMutexGroup("barFizzle", "Bar", "Fizzle"); - } } diff --git a/examples/example-oauth/build.gradle b/examples/example-oauth/build.gradle deleted file mode 100644 index 806bbdd6a..000000000 --- a/examples/example-oauth/build.gradle +++ /dev/null @@ -1,14 +0,0 @@ -apply plugin: 'org.springframework.boot' - -dependencies { - implementation project(":edison-oauth") - - implementation libraries.togglz_spring_web - testImplementation project(":edison-testsupport") -} - -artifacts { - archives jar - archives javadocJar - archives sourcesJar -} diff --git a/examples/example-oauth/src/main/java/de/otto/edison/example/ApiController.java b/examples/example-oauth/src/main/java/de/otto/edison/example/ApiController.java deleted file mode 100644 index f3ef9e9d2..000000000 --- a/examples/example-oauth/src/main/java/de/otto/edison/example/ApiController.java +++ /dev/null @@ -1,24 +0,0 @@ -package de.otto.edison.example; - -import org.springframework.http.MediaType; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import static org.springframework.web.bind.annotation.RequestMethod.GET; - -@RestController -public class ApiController { - - @RequestMapping( - value = "/api/hello", - produces = MediaType.APPLICATION_JSON_VALUE, - method = GET) - @ResponseBody - @PreAuthorize("#oauth2.hasScope('hello.read')") - public String sayHelloAsHtml() { - return "{\"hello\": \"world\"}"; - } - -} diff --git a/examples/example-oauth/src/main/java/de/otto/edison/example/ExampleOauthServer.java b/examples/example-oauth/src/main/java/de/otto/edison/example/ExampleOauthServer.java deleted file mode 100644 index dad34c141..000000000 --- a/examples/example-oauth/src/main/java/de/otto/edison/example/ExampleOauthServer.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.otto.edison.example; - -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.PropertySource; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; - -import static org.springframework.boot.SpringApplication.run; - -@PropertySource("version.properties") -@SpringBootApplication(scanBasePackages = "de.otto.edison") -@EnableScheduling -public class ExampleOauthServer { - - public static void main(String[] args) { - run(ExampleOauthServer.class, args); - } - -} diff --git a/examples/example-oauth/src/main/java/de/otto/edison/example/OAuthController.java b/examples/example-oauth/src/main/java/de/otto/edison/example/OAuthController.java deleted file mode 100644 index feda7f6b1..000000000 --- a/examples/example-oauth/src/main/java/de/otto/edison/example/OAuthController.java +++ /dev/null @@ -1,46 +0,0 @@ -package de.otto.edison.example; - -import de.otto.edison.example.oauth.OAuthService; -import de.otto.edison.oauth.OAuthPublicKey; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.jwt.Jwt; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; - -import java.util.Collections; -import java.util.List; - -import static org.springframework.web.bind.annotation.RequestMethod.GET; - -@RestController -public class OAuthController { - - private final OAuthService oAuthService; - - @Autowired - public OAuthController(final OAuthService oAuthService) { - this.oAuthService = oAuthService; - } - - @RequestMapping( - value = "/token", - produces = "application/json", - method = GET - ) - @ResponseBody - public Jwt getTestToken() { - return oAuthService.getExampleJWTToken(); - } - - @RequestMapping( - value = "/publicKey", - produces = "application/json", - method = GET - ) - @ResponseBody - public List getPublicKey() { - return Collections.singletonList(oAuthService.getPublicKey()); - } - -} diff --git a/examples/example-oauth/src/main/java/de/otto/edison/example/configuration/OAuthConfig.java b/examples/example-oauth/src/main/java/de/otto/edison/example/configuration/OAuthConfig.java deleted file mode 100644 index 5cd5d986f..000000000 --- a/examples/example-oauth/src/main/java/de/otto/edison/example/configuration/OAuthConfig.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.otto.edison.example.configuration; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; - -@Configuration -public class OAuthConfig { - - @Bean - public KeyPair keyPair() throws NoSuchAlgorithmException { - return KeyPairGenerator.getInstance("RSA").generateKeyPair(); - } -} diff --git a/examples/example-oauth/src/main/java/de/otto/edison/example/oauth/OAuthService.java b/examples/example-oauth/src/main/java/de/otto/edison/example/oauth/OAuthService.java deleted file mode 100644 index 83398d67a..000000000 --- a/examples/example-oauth/src/main/java/de/otto/edison/example/oauth/OAuthService.java +++ /dev/null @@ -1,61 +0,0 @@ -package de.otto.edison.example.oauth; - -import de.otto.edison.oauth.OAuthPublicKey; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.jwt.Jwt; -import org.springframework.security.jwt.JwtHelper; -import org.springframework.security.jwt.crypto.sign.RsaSigner; -import org.springframework.stereotype.Service; - -import java.security.KeyPair; -import java.security.interfaces.RSAPrivateKey; -import java.time.ZonedDateTime; -import java.util.Base64; - -import static de.otto.edison.oauth.OAuthPublicKey.oAuthPublicKeyBuilder; - -@Service -public class OAuthService { - - private static final ZonedDateTime VALID_FROM = ZonedDateTime.now().minusDays(1); - private static final ZonedDateTime VALID_UNTIL = ZonedDateTime.now().plusDays(1); - private final KeyPair keyPair; - - @Autowired - public OAuthService(final KeyPair keyPair) { - this.keyPair = keyPair; - } - - public Jwt getExampleJWTToken() { - final ZonedDateTime soon = ZonedDateTime.now().plusDays(365); - final String jwtToken = "{\n" + - " \"aud\": [\n" + - " \"https://api.otto.de/api-authorization\"\n" + - " ],\n" + - " \"exp\": " + soon.toInstant().getEpochSecond() + ",\n" + - " \"user_name\": \"3d44bbc24614e28edd094bc54ef0497809717af5\",\n" + - " \"jti\": \"3cee521d-96a7-4d82-b726-7e02355f3a55\",\n" + - " \"client_id\": \"fe0661e5a99e4d43bd3496cc6c58025f\",\n" + - " \"scope\": [\n" + - " \"hello.read\"\n" + - " ]\n" + - "}"; - final RsaSigner rsaSigner = new RsaSigner((RSAPrivateKey) keyPair.getPrivate()); - - return JwtHelper.encode(jwtToken, rsaSigner); - } - - public OAuthPublicKey getPublicKey() { - final String publicKeyStringRepresentation = "-----BEGIN PUBLIC KEY-----\n" + - new String(Base64.getEncoder().encode(this.keyPair.getPublic().getEncoded())) + - "\n-----END PUBLIC KEY-----"; - - return oAuthPublicKeyBuilder() - .withValidUntil(VALID_UNTIL) - .withValidFrom(VALID_FROM) - .withPublicKeyFingerprint("fingerprint") - .withPublicKey(publicKeyStringRepresentation) - .build(); - - } -} diff --git a/examples/example-oauth/src/main/resources/application.yml b/examples/example-oauth/src/main/resources/application.yml deleted file mode 100644 index 13a8fb8c3..000000000 --- a/examples/example-oauth/src/main/resources/application.yml +++ /dev/null @@ -1,45 +0,0 @@ -spring: - application: - name: example-oauth - jackson: - date-format: yyyy-MM-dd'T'hh:mm:ss.sssZ - serialization: - indent-output: true - -server: - servlet: - context-path: / - port: 8080 - -management: - endpoints: - web: - base-path: /actuator - expose: '*' - -edison: - gracefulshutdown: - enabled: false - application: - name: status - description: Example service to show how to use edison-core in your microservices - environment: local - group: example - title: Example Status - status: - team: - business-contact: edison-team@example.org - name: Edison Team - technical-contact: edison-dev@example.org - - # OAuth Configuration goes here - oauth: - public-key: - enabled: true - url: http://localhost:8080/publicKey - interval: 1800000 - authorization: - resource: - patterns: "/api/**" - jwt: - audience: https://api.otto.de/api-authorization \ No newline at end of file diff --git a/examples/example-oauth/src/main/resources/logback-spring.xml b/examples/example-oauth/src/main/resources/logback-spring.xml deleted file mode 100644 index 86df3842e..000000000 --- a/examples/example-oauth/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - example - - - - %d{ISO8601} %-5p logger="%c" message="%m" thread="%t" %n - - - - - - - - diff --git a/examples/example-oauth/src/main/resources/version.properties b/examples/example-oauth/src/main/resources/version.properties deleted file mode 100644 index 3934e93fd..000000000 --- a/examples/example-oauth/src/main/resources/version.properties +++ /dev/null @@ -1,6 +0,0 @@ -# Properties for VCS information. This is used to configure the VersionInfo which is part of the status page. -# This file is typically generated during the build of the application. -# -edison.status.vcs.commit= -edison.status.vcs.version=pre 2.0.0 -edison.status.vcs.url-template=https://github.com/otto-de/edison-microservice \ No newline at end of file diff --git a/examples/example-oauth/src/test/java/de/otto/edison/example/ApiControllerIntegrationTest.java b/examples/example-oauth/src/test/java/de/otto/edison/example/ApiControllerIntegrationTest.java deleted file mode 100644 index fb737ad78..000000000 --- a/examples/example-oauth/src/test/java/de/otto/edison/example/ApiControllerIntegrationTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package de.otto.edison.example; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.skyscreamer.jsonassert.JSONAssert; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.http.HttpHeaders.ACCEPT; -import static org.springframework.http.HttpHeaders.AUTHORIZATION; - -@ExtendWith(SpringExtension.class) -@SpringBootTest(classes = ExampleOauthServer.class, webEnvironment = RANDOM_PORT) -@ActiveProfiles("test") -public class ApiControllerIntegrationTest { - - private String baseUrl; - - @Autowired - private HttpClient asyncHttpClient; - - @Autowired - private OAuthTestHelper oAuthTestHelper; - - @LocalServerPort - private int port; - - - @BeforeEach - public void setUp() { - baseUrl = String.format("http://localhost:%d", port); - } - - @Test - public void shouldReturnHelloResponseWithValidOauthToken() throws Exception { - // Given - final String bearerToken = oAuthTestHelper.getBearerToken("hello.read"); - - // When - final HttpResponse response = asyncHttpClient - .send(HttpRequest.newBuilder() - .GET() - .uri(URI.create(baseUrl + "/api/hello?context=mode")) - .header(AUTHORIZATION, bearerToken) - .header(ACCEPT, MediaType.APPLICATION_JSON_VALUE) - .build(), HttpResponse.BodyHandlers.ofString()); - - // Then - assertThat(response.statusCode(), is(200)); - assertThat(response.body(), is("{\"hello\": \"world\"}")); - } - - @Test - public void shouldReturn403WhenRequestingWithInvalidScopeInOauthToken() throws Exception { - // Given - final String bearerToken = oAuthTestHelper.getBearerToken("hello.write"); - - // When - final HttpResponse response = asyncHttpClient - .send(HttpRequest.newBuilder() - .GET() - .uri(URI.create(baseUrl + "/api/hello?context=mode")) - .header(AUTHORIZATION, bearerToken) - .header(ACCEPT, MediaType.APPLICATION_JSON_VALUE) - .build(), HttpResponse.BodyHandlers.ofString()); - - // Then - assertThat(response.statusCode(), is(403)); - - String expectedJson = "{\n" + - " \"error_description\" : \"Insufficient scope for this resource\",\n" + - " \"error\" : \"forbidden\"\n" + - "}"; - JSONAssert.assertEquals(expectedJson, response.body().toString(), false); - } - - @Test - public void shouldReturn403WhenRequestingWithInvalidOauthToken() throws Exception { - // Given - final String bearerToken = "someInvalidBearerToken"; - - // When - final HttpResponse response = asyncHttpClient - .send(HttpRequest.newBuilder() - .GET() - .uri(URI.create(baseUrl + "/api/hello?context=mode")) - .header(AUTHORIZATION, bearerToken) - .header(ACCEPT, MediaType.APPLICATION_JSON_VALUE) - .build(), HttpResponse.BodyHandlers.ofString()); - - // Then - assertThat(response.statusCode(), is(403)); - String expectedJson = "{\n" + - " \"error_description\" : \"Insufficient scope for this resource\",\n" + - " \"error\" : \"forbidden\"\n" + - "}"; - JSONAssert.assertEquals(expectedJson, response.body().toString(), false); - } - - @Test - public void shouldReturn403WhenRequestingWithoutOauthToken() throws Exception { - // Given - - // When - final HttpResponse response = asyncHttpClient - .send(HttpRequest.newBuilder() - .GET() - .uri(URI.create(baseUrl + "/api/hello?context=mode")) - .header(ACCEPT, MediaType.APPLICATION_JSON_VALUE) - .build(), HttpResponse.BodyHandlers.ofString()); - - // Then - assertThat(response.statusCode(), is(403)); - String expectedJson = "{\n" + - " \"error_description\" : \"Insufficient scope for this resource\",\n" + - " \"error\" : \"forbidden\"\n" + - "}"; - JSONAssert.assertEquals(expectedJson, response.body().toString(), false); - } -} diff --git a/examples/example-oauth/src/test/java/de/otto/edison/example/OAuthTestHelper.java b/examples/example-oauth/src/test/java/de/otto/edison/example/OAuthTestHelper.java deleted file mode 100644 index 644c6c2ab..000000000 --- a/examples/example-oauth/src/test/java/de/otto/edison/example/OAuthTestHelper.java +++ /dev/null @@ -1,47 +0,0 @@ -package de.otto.edison.example; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.jwt.Jwt; -import org.springframework.security.jwt.JwtHelper; -import org.springframework.security.jwt.crypto.sign.RsaSigner; -import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; -import org.springframework.stereotype.Component; - -import java.security.KeyPair; -import java.security.interfaces.RSAPrivateKey; -import java.time.ZonedDateTime; - -@Component -public class OAuthTestHelper { - - @Autowired - AuthorizationServerTokenServices tokenservice; - - @Autowired - KeyPair keyPair; - - @Value("${edison.oauth.jwt.audience}") - String aud; - - public String getBearerToken(final String scope) { - final ZonedDateTime soon = ZonedDateTime.now().plusDays(365); - final String jwtToken = "{\n" + - " \"aud\": [\n" + - " \"" + aud + "\"\n" + - " ],\n" + - " \"exp\": " + soon.toEpochSecond() + ",\n" + - " \"user_name\": \"3d44bbc24614e28edd094bc54ef0497809717af5\",\n" + - " \"jti\": \"3cee521d-96a7-4d82-b726-7e02355f3a55\",\n" + - " \"client_id\": \"fe0661e5a99e4d43bd3496cc6c58025f\",\n" + - " \"scope\": [\n" + - " \"" + scope + "\"\n" + - " ]\n" + - "}"; - final RsaSigner rsaSigner = new RsaSigner((RSAPrivateKey) keyPair.getPrivate()); - final Jwt encode = JwtHelper.encode(jwtToken, rsaSigner); - - - return "Bearer " + encode.getEncoded(); - } -} diff --git a/examples/example-oauth/src/test/java/de/otto/edison/example/configuration/OAuthPublicKeyTestConfiguration.java b/examples/example-oauth/src/test/java/de/otto/edison/example/configuration/OAuthPublicKeyTestConfiguration.java deleted file mode 100644 index a6b85f139..000000000 --- a/examples/example-oauth/src/test/java/de/otto/edison/example/configuration/OAuthPublicKeyTestConfiguration.java +++ /dev/null @@ -1,96 +0,0 @@ -package de.otto.edison.example.configuration; - -import de.otto.edison.oauth.KeyExchangeJwtAccessTokenConverter; -import de.otto.edison.oauth.OAuthPublicKey; -import de.otto.edison.oauth.OAuthPublicKeyRepository; -import de.otto.edison.oauth.OAuthPublicKeyStore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.context.annotation.Profile; -import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; -import org.springframework.security.oauth2.provider.token.DefaultTokenServices; -import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; -import org.togglz.core.user.SimpleFeatureUser; -import org.togglz.core.user.UserProvider; - -import java.net.http.HttpClient; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.RSAPublicKey; -import java.time.Duration; -import java.util.Base64; -import java.util.Collections; -import java.util.List; - -import static java.time.ZonedDateTime.now; - -@Configuration -public class OAuthPublicKeyTestConfiguration { - - @Bean - @Profile("test") - public HttpClient asyncHttpClient() { - return HttpClient.newBuilder().connectTimeout(Duration.ofMillis(1000)).build(); - } - - @Bean - @Profile("test") - public KeyPair testKeyPair() throws NoSuchAlgorithmException { - final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); - return keyGen.generateKeyPair(); - } - - @Bean - @Profile("test") - public OAuthPublicKeyStore testOAuthPublicKeyStore(HttpClient asyncHttpClient, OAuthPublicKeyRepository inMemoryTestRepository) { - return new OAuthPublicKeyStore("http://localhost/access/publicKeys", asyncHttpClient, inMemoryTestRepository); - } - - @Bean - @ConditionalOnMissingBean(UserProvider.class) - public UserProvider userProvider() { - return () -> new SimpleFeatureUser("someUser"); - } - - @Bean - @Profile("test") - public AuthorizationServerTokenServices testAuthorizationServerTokenServices(final KeyExchangeJwtAccessTokenConverter keyExchangeJwtAccessTokenConverter, - final KeyPair keyPair) { - final DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); - keyExchangeJwtAccessTokenConverter.setKeyPair(keyPair); - defaultTokenServices.setTokenStore(new JwtTokenStore(keyExchangeJwtAccessTokenConverter)); - return defaultTokenServices; - } - - @Bean - @Profile("test") - @Primary - public OAuthPublicKeyRepository inMemoryTestRepository(final KeyPair keyPair) { - return new OAuthPublicKeyRepository() { - @Override - public void refreshPublicKeys(final List publicKeys) { - // do nothing - } - - @Override - public List retrieveActivePublicKeys() { - - final RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); - final String verifierKey = "-----BEGIN PUBLIC KEY-----\n" + - Base64.getEncoder().encodeToString(publicKey.getEncoded()) + - "\n-----END PUBLIC KEY-----"; - - final OAuthPublicKey oAuthPublicKey = OAuthPublicKey - .oAuthPublicKeyBuilder() - .withPublicKey(verifierKey) - .withPublicKeyFingerprint("someFingerprint") - .withValidFrom(now().minusHours(1)) - .build(); - return Collections.singletonList(oAuthPublicKey); - } - }; - } -} diff --git a/examples/example-oauth/src/test/resources/application-test.yml b/examples/example-oauth/src/test/resources/application-test.yml deleted file mode 100644 index d9b443530..000000000 --- a/examples/example-oauth/src/test/resources/application-test.yml +++ /dev/null @@ -1,9 +0,0 @@ -edison: - oauth: - public-key: - enabled: false - authorization: - resource: - patterns: "/" - jwt: - audience: https://api.otto.de/api-authorization \ No newline at end of file diff --git a/gradle/build.gradle b/gradle/build.gradle index 1e560385a..1c570f252 100644 --- a/gradle/build.gradle +++ b/gradle/build.gradle @@ -10,7 +10,7 @@ */ ext { versions = [ - spring_boot : '2.5.6', + spring_boot : '2.6.0', spring : '5.3.13', spring_security_core : '5.6.0', spring_security_web : '5.6.0',