package io.vertx.ext.auth.oauth2.impl;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.auth.PubSecKeyOptions;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.impl.AuthProviderInternal;
import io.vertx.ext.jwt.JWK;
import io.vertx.ext.jwt.JWT;
import io.vertx.ext.auth.oauth2.*;
import io.vertx.ext.auth.oauth2.impl.flow.*;
import static io.vertx.ext.auth.oauth2.impl.OAuth2API.fetch;
public class OAuth2AuthProviderImpl implements OAuth2Auth, AuthProviderInternal {
private static final Logger LOG = LoggerFactory.getLogger(OAuth2AuthProviderImpl.class);
private final Vertx vertx;
private final OAuth2ClientOptions config;
private final JWT jwt = new JWT();
private final OAuth2Flow flow;
private OAuth2RBAC rbac;
public OAuth2AuthProviderImpl(Vertx vertx, OAuth2ClientOptions config) {
this.vertx = vertx;
this.config = config;
if (config.getPubSecKeys() != null) {
for (PubSecKeyOptions pubSecKey : config.getPubSecKeys()) {
if (pubSecKey.isSymmetric()) {
jwt.addJWK(new JWK(pubSecKey.getAlgorithm(), pubSecKey.getPublicKey()));
} else {
jwt.addJWK(new JWK(pubSecKey.getAlgorithm(), pubSecKey.isCertificate(), pubSecKey.getPublicKey(), pubSecKey.getSecretKey()));
}
}
}
switch (config.getFlow()) {
case AUTH_CODE:
flow = new AuthCodeImpl(this);
break;
case CLIENT:
flow = new ClientImpl(this);
break;
case PASSWORD:
flow = new PasswordImpl(this);
break;
case AUTH_JWT:
flow = new AuthJWTImpl(this);
break;
default:
throw new IllegalArgumentException("Unsupported oauth2 flow type: " + config.getFlow());
}
}
@Override
public void verifyIsUsingPassword() {
if (getFlowType() != OAuth2FlowType.PASSWORD) {
throw new IllegalArgumentException("OAuth2Auth + Basic Auth requires OAuth2 PASSWORD flow");
}
}
@Override
public OAuth2Auth loadJWK(Handler<AsyncResult<Void>> handler) {
final JsonObject headers = new JsonObject();
headers.put("Accept", "application/json");
fetch(
vertx,
config,
HttpMethod.GET,
config.getJwkPath(),
headers,
null,
res -> {
if (res.failed()) {
handler.handle(Future.failedFuture(res.cause()));
return;
}
final OAuth2Response reply = res.result();
if (reply.body() == null || reply.body().length() == 0) {
handler.handle(Future.failedFuture("No Body"));
return;
}
JsonObject json;
if (reply.is("application/json")) {
try {
json = reply.jsonObject();
} catch (RuntimeException e) {
handler.handle(Future.failedFuture(e));
return;
}
} else {
handler.handle(Future.failedFuture("Cannot handle content type: " + reply.headers().get("Content-Type")));
return;
}
try {
if (json.containsKey("error")) {
String description;
Object error = json.getValue("error");
if (error instanceof JsonObject) {
description = ((JsonObject) error).getString("message");
} else {
try {
description = json.getString("error_description", json.getString("error"));
} catch (RuntimeException e) {
description = error.toString();
}
}
handler.handle(Future.failedFuture(description));
} else {
JsonArray keys = json.getJsonArray("keys");
for (Object key : keys) {
try {
jwt.addJWK(new JWK((JsonObject) key));
} catch (RuntimeException e) {
LOG.warn("Skipped unsupported JWK: " + e.getMessage());
}
}
handler.handle(Future.succeededFuture());
}
} catch (RuntimeException e) {
handler.handle(Future.failedFuture(e));
}
});
return this;
}
@Override
public OAuth2Auth rbacHandler(OAuth2RBAC rbac) {
if (this.rbac != null) {
throw new IllegalStateException("There is already a RBAC handler registered");
}
this.rbac = rbac;
return this;
}
OAuth2RBAC getRBACHandler() {
return rbac;
}
public OAuth2ClientOptions getConfig() {
return config;
}
public Vertx getVertx() {
return vertx;
}
public JWT getJWT() {
return jwt;
}
@Override
public void authenticate(JsonObject authInfo, Handler<AsyncResult<User>> resultHandler) {
if (
authInfo.containsKey("token_type") && "Bearer".equalsIgnoreCase(authInfo.getString("token_type")) &&
authInfo.containsKey("access_token") && authInfo.getString("access_token") != null) {
final AccessToken oauth2Token = new OAuth2TokenImpl(this, authInfo);
if (oauth2Token.accessToken() == null || jwt.isUnsecure()) {
oauth2Token.introspect(introspect -> {
if (introspect.failed()) {
resultHandler.handle(Future.failedFuture(introspect.cause()));
return;
}
if (oauth2Token.expired()) {
resultHandler.handle(Future.failedFuture("Expired token"));
return;
}
resultHandler.handle(Future.succeededFuture(oauth2Token));
});
} else {
if (oauth2Token.expired()) {
resultHandler.handle(Future.failedFuture("Expired Token"));
} else {
resultHandler.handle(Future.succeededFuture(oauth2Token));
}
}
} else {
flow.getToken(authInfo, getToken -> {
if (getToken.failed()) {
resultHandler.handle(Future.failedFuture(getToken.cause()));
} else {
resultHandler.handle(Future.succeededFuture(getToken.result()));
}
});
}
}
@Override
public String authorizeURL(JsonObject params) {
return flow.authorizeURL(params);
}
@Override
@Deprecated
public void getToken(JsonObject credentials, Handler<AsyncResult<AccessToken>> handler) {
flow.getToken(credentials, handler);
}
@Override
@Deprecated
public OAuth2Auth decodeToken(String token, Handler<AsyncResult<AccessToken>> handler) {
authenticate(new JsonObject().put("access_token", token).put("token_type", "Bearer"), auth -> {
if (auth.succeeded()) {
handler.handle(Future.succeededFuture((AccessToken) auth.result()));
} else {
handler.handle(Future.failedFuture(auth.cause()));
}
});
return this;
}
@Override
public OAuth2Auth introspectToken(String token, String tokenType, Handler<AsyncResult<AccessToken>> handler) {
try {
final AccessToken accessToken = new OAuth2TokenImpl(this, new JsonObject().put(tokenType, token));
if (accessToken.expired()) {
handler.handle(Future.failedFuture("Expired token"));
return this;
}
accessToken.introspect(introspect -> {
if (introspect.failed()) {
handler.handle(Future.failedFuture(introspect.cause()));
return;
}
if (accessToken.expired()) {
handler.handle(Future.failedFuture("Expired token"));
return;
}
handler.handle(Future.succeededFuture(accessToken));
});
} catch (RuntimeException e) {
handler.handle(Future.failedFuture(e));
}
return this;
}
@Override
@Deprecated
public String getScopeSeparator() {
final String sep = config.getScopeSeparator();
return sep == null ? " " : sep;
}
@Override
public OAuth2FlowType getFlowType() {
return config.getFlow();
}
public OAuth2Flow getFlow() {
return flow;
}
}