From ca0bd8d67a95b967272854cc937ca23a78b71fac Mon Sep 17 00:00:00 2001 From: Peter Schuller Date: Fri, 17 Dec 2021 07:56:58 +0100 Subject: [PATCH] [wip] minor additions --- src/de/pzzz/vertx/oauth/OAuthConfig.java | 53 ++++++++++++ src/de/pzzz/vertx/oauth/OAuthToken.java | 59 +++++++++++++ src/de/pzzz/vertx/oauth/OAuthWebClient.java | 96 +++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100755 src/de/pzzz/vertx/oauth/OAuthConfig.java create mode 100755 src/de/pzzz/vertx/oauth/OAuthToken.java create mode 100755 src/de/pzzz/vertx/oauth/OAuthWebClient.java diff --git a/src/de/pzzz/vertx/oauth/OAuthConfig.java b/src/de/pzzz/vertx/oauth/OAuthConfig.java new file mode 100755 index 0000000..80c5fda --- /dev/null +++ b/src/de/pzzz/vertx/oauth/OAuthConfig.java @@ -0,0 +1,53 @@ +package de.pzzz.vertx.oauth; + +import java.io.Serializable; + +public class OAuthConfig implements Serializable { + private static final long serialVersionUID = -2962157804887273549L; + + private String oauthClientId; + private String oauthClientSecret; + private String oauthTokenUrl; + private String baseUrl; + private boolean trustAllCertificates; + + public String getOauthClientId() { + return oauthClientId; + } + + public void setOauthClientId(final String oauthClientId) { + this.oauthClientId = oauthClientId; + } + + public String getOauthClientSecret() { + return oauthClientSecret; + } + + public void setOauthClientSecret(final String oauthClientSecret) { + this.oauthClientSecret = oauthClientSecret; + } + + public String getOauthTokenUrl() { + return oauthTokenUrl; + } + + public void setOauthTokenUrl(final String oauthTokenUrl) { + this.oauthTokenUrl = oauthTokenUrl; + } + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(final String baseUrl) { + this.baseUrl = baseUrl; + } + + public boolean isTrustAllCertificates() { + return trustAllCertificates; + } + + public void setTrustAllCertificates(final boolean trustAllCertificates) { + this.trustAllCertificates = trustAllCertificates; + } +} diff --git a/src/de/pzzz/vertx/oauth/OAuthToken.java b/src/de/pzzz/vertx/oauth/OAuthToken.java new file mode 100755 index 0000000..84c2969 --- /dev/null +++ b/src/de/pzzz/vertx/oauth/OAuthToken.java @@ -0,0 +1,59 @@ +package de.pzzz.vertx.oauth; + +import java.io.Serializable; +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents an OAuth2 Token as received from IAM via client credentials flow. + */ +public class OAuthToken implements Serializable { + private static final long serialVersionUID = -1712414758315937247L; + + private static final Integer EXPIRATION_OFFSET = 30; + + @JsonProperty("access_token") + private String token; + @JsonProperty("token_type") + private String type; + @JsonProperty("expires_in") + private Integer expiresIn; + + @JsonIgnore + private final Date issued; + + public OAuthToken() { + issued = new Date(); + } + + public boolean isValid() { + int diffSeconds = (int) Math.ceil((new Date().getTime() - issued.getTime())/1000f); + return diffSeconds < (expiresIn - EXPIRATION_OFFSET); + } + + public String getToken() { + return token; + } + + public void setToken(final String token) { + this.token = token; + } + + public String getType() { + return type; + } + + public void setType(final String type) { + this.type = type; + } + + public Integer getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(final Integer expiresIn) { + this.expiresIn = expiresIn; + } +} diff --git a/src/de/pzzz/vertx/oauth/OAuthWebClient.java b/src/de/pzzz/vertx/oauth/OAuthWebClient.java new file mode 100755 index 0000000..da40f71 --- /dev/null +++ b/src/de/pzzz/vertx/oauth/OAuthWebClient.java @@ -0,0 +1,96 @@ +package de.pzzz.vertx.oauth; + +import java.util.logging.Logger; + +import io.vertx.core.Future; +import io.vertx.core.MultiMap; +import io.vertx.core.Promise; +import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpMethod; +import io.vertx.ext.web.client.HttpRequest; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.client.WebClientOptions; +import io.vertx.ext.web.codec.BodyCodec; + +public class OAuthWebClient { + private static final Logger LOG = Logger.getLogger(OAuthWebClient.class.getName()); + + private final WebClient webClient; + private final OAuthConfig config; + private OAuthToken token; + + public OAuthWebClient(final Vertx vertx, final OAuthConfig config) { + this.webClient = WebClient.create(vertx, new WebClientOptions() + .setLogActivity(true).setSsl(true).setTrustAll(config.isTrustAllCertificates()).setVerifyHost(false)); + this.config = config; + } + + public Future> prepareAuthenticatedPut(final String requestPath) { + return prepareAuthenticatedRequest(HttpMethod.PUT, requestPath); + } + + public Future> prepareAuthenticatedGet(final String requestPath) { + return prepareAuthenticatedRequest(HttpMethod.GET, requestPath); + } + + public Future> prepareAuthenticatedPost(final String requestPath) { + return prepareAuthenticatedRequest(HttpMethod.POST, requestPath); + } + + public Future> prepareAuthenticatedDelete(final String requestPath) { + return prepareAuthenticatedRequest(HttpMethod.DELETE, requestPath); + } + + public Future> prepareAuthenticatedRequest(final HttpMethod method, final String requestPath) { + Promise> promise = Promise.promise(); + ensureIsAuthenticated().onComplete(v -> + promise.complete(webClient.requestAbs(method, config.getBaseUrl() + requestPath) + .bearerTokenAuthentication(token.getToken())) + ).onFailure(promise::fail); + return promise.future(); + } + + private Future ensureIsAuthenticated() { + if (null != token && token.isValid()) { + LOG.finest("keeping token..."); + return Future.succeededFuture(); + } + LOG.finest("Requesting new token..."); + return getToken(); + } + + private Future getToken() { + String baseErrorMessage = "Failed to login! "; + Promise promise = Promise.promise(); + MultiMap form = MultiMap.caseInsensitiveMultiMap(); + form.set("grant_type", "client_credentials"); + LOG.fine("Requesting new token..."); + webClient.postAbs(config.getOauthTokenUrl()) + .basicAuthentication(config.getOauthClientId(), config.getOauthClientSecret()) + .as(BodyCodec.json(OAuthToken.class)) + .sendForm(form) + .onSuccess(response -> { + if (response.statusCode() == 200) { + token = response.body(); + LOG.finest("Got new token..."); + promise.complete(); + } else { + String errorMessage = baseErrorMessage + response.statusCode() + " - " + + response.statusMessage(); + handleTokenError(errorMessage, promise); + } + }) + .onFailure(error -> { + String errorMessage = baseErrorMessage + error.getMessage(); + handleTokenError(errorMessage, promise); + }); + return promise.future(); + } + + private void handleTokenError(final String errorMessage, final Promise promise) { + LOG.severe(errorMessage); + token = null; + promise.fail(errorMessage); + } +}