
io.vertx.ext.auth.oauth2.OAuth2Options Maven / Gradle / Ivy
/*
* Copyright (c) 2011-2014 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.ext.auth.oauth2;
import io.vertx.codegen.annotations.DataObject;
import io.vertx.codegen.annotations.Fluent;
import io.vertx.codegen.json.annotations.JsonGen;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.internal.logging.Logger;
import io.vertx.core.internal.logging.LoggerFactory;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.JWTOptions;
import io.vertx.ext.auth.PubSecKeyOptions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Options describing how an OAuth2 {@link HttpClient} will make connections.
*
* @author Paulo Lopes
*/
@DataObject
@JsonGen(publicConverter = false)
public class OAuth2Options {
private static final Logger LOG = LoggerFactory.getLogger(OAuth2Options.class);
// Defaults
private static final boolean BASIC_AUTHORIZATION = true;
private static final String AUTHORIZATION_PATH = "/oauth/authorize";
private static final String TOKEN_PATH = "/oauth/token";
private static final String REVOCATION_PATH = "/oauth/revoke";
private static final String SCOPE_SEPARATOR = " ";
private static final boolean VALIDATE_ISSUER = true;
//seconds of JWK's default age (-1 means no rotation)
private static final long JWK_DEFAULT_AGE = -1L;
private List supportedGrantTypes;
private String authorizationPath;
private String tokenPath;
private String revocationPath;
private String scopeSeparator;
// this is an openid-connect extension
private boolean validateIssuer;
private String logoutPath;
private String userInfoPath;
// extra parameters to be added while requesting the user info
private JsonObject userInfoParams;
// introspection RFC7662
private String introspectionPath;
// JWK path RFC7517
private String jwkPath;
//seconds of JWKs lifetime
private long jwkMaxAge;
// OpenID non standard
private String tenant;
private String site;
private String clientId;
private String clientSecret;
private boolean useBasicAuthorization;
//https://tools.ietf.org/html/rfc7521
private String clientAssertionType;
private String clientAssertion;
private String userAgent;
private JsonObject headers;
private List pubSecKeys;
private JWTOptions jwtOptions;
// extra parameters to be added while requesting a token
private JsonObject extraParams;
// client config
private HttpClientOptions httpClientOptions = new HttpClientOptions();
private List jwks;
public String getSite() {
return site;
}
/**
* Default constructor
*/
public OAuth2Options() {
init();
}
/**
* Copy constructor
*
* @param other the options to copy
*/
public OAuth2Options(OAuth2Options other) {
tenant = other.getTenant();
clientId = other.getClientId();
clientSecret = other.getClientSecret();
useBasicAuthorization = other.isUseBasicAuthorization();
clientAssertionType = other.getClientAssertionType();
clientAssertion = other.getClientAssertion();
validateIssuer = other.isValidateIssuer();
authorizationPath = other.getAuthorizationPath();
tokenPath = other.getTokenPath();
revocationPath = other.getRevocationPath();
userInfoPath = other.getUserInfoPath();
introspectionPath = other.getIntrospectionPath();
scopeSeparator = other.getScopeSeparator();
site = other.getSite();
if (other.pubSecKeys == null) {
pubSecKeys = null;
} else {
List list = new ArrayList<>(other.pubSecKeys.size());
for (PubSecKeyOptions pubSecKey : other.pubSecKeys) {
list.add(new PubSecKeyOptions(pubSecKey));
}
pubSecKeys = list;
}
jwtOptions = other.jwtOptions == null ? null : new JWTOptions(other.jwtOptions);
logoutPath = other.getLogoutPath();
extraParams = other.extraParams == null ? null : other.extraParams.copy();
userInfoParams = other.userInfoParams == null ? null : other.userInfoParams.copy();
headers = other.headers == null ? null : other.headers.copy();
jwkPath = other.getJwkPath();
jwkMaxAge = other.getJwkMaxAgeInSeconds();
httpClientOptions = other.httpClientOptions == null ? null : new HttpClientOptions(other.httpClientOptions);
userAgent = other.getUserAgent();
supportedGrantTypes = other.supportedGrantTypes == null ? null : new ArrayList<>(other.supportedGrantTypes);
if (other.jwks == null) {
jwks = null;
} else {
List list = new ArrayList<>(other.jwks.size());
for (JsonObject jwk : other.jwks) {
list.add(jwk.copy());
}
jwks = list;
}
// compute paths with variables, at this moment it is only relevant that
// the paths and site are properly computed
replaceVariables(false);
}
private void init() {
validateIssuer = VALIDATE_ISSUER;
authorizationPath = AUTHORIZATION_PATH;
tokenPath = TOKEN_PATH;
revocationPath = REVOCATION_PATH;
scopeSeparator = SCOPE_SEPARATOR;
jwtOptions = new JWTOptions();
jwkMaxAge = JWK_DEFAULT_AGE;
useBasicAuthorization = BASIC_AUTHORIZATION;
}
/**
* Constructor to create an options from JSON
*
* @param json the JSON
*/
public OAuth2Options(JsonObject json) {
init();
OAuth2OptionsConverter.fromJson(json, this);
// compute paths with variables, at this moment it is only relevant that
// the paths and site are properly computed
replaceVariables(false);
}
/**
* Get the Oauth2 authorization resource path. e.g.: /oauth/authorize
*
* @return authorization path
*/
public String getAuthorizationPath() {
return authorizationPath;
}
public OAuth2Options setAuthorizationPath(String authorizationPath) {
this.authorizationPath = authorizationPath;
return this;
}
/**
* Get the Oauth2 token resource path. e.g.: /oauth/token
*
* @return token path
*/
public String getTokenPath() {
return tokenPath;
}
public OAuth2Options setTokenPath(String tokenPath) {
this.tokenPath = tokenPath;
return this;
}
/**
* Get the Oauth2 revocation resource path. e.g.: /oauth/revoke
*
* @return revocation path
*/
public String getRevocationPath() {
return revocationPath;
}
/**
* Set the Oauth2 revocation resource path. e.g.: /oauth/revoke
*
* @return self
*/
public OAuth2Options setRevocationPath(String revocationPath) {
this.revocationPath = revocationPath;
return this;
}
/**
* Root URL for the provider without trailing slashes
*
* @param site a url
* @return self
*/
public OAuth2Options setSite(String site) {
this.site = site;
return this;
}
/**
* Get the provider client id
*
* @return client id
*/
public String getClientId() {
return clientId;
}
/**
* Set the provider client id
*
* @param clientId client id
* @return self
*/
public OAuth2Options setClientId(String clientId) {
this.clientId = clientId;
return this;
}
/**
* Get the provider client secret
*
* @return the client secret
*/
public String getClientSecret() {
return clientSecret;
}
/**
* Set the provider client secret
*
* @param clientSecret client secret
* @return self
*/
public OAuth2Options setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}
public OAuth2Options setUseBasicAuthorization(boolean useBasicAuthorization) {
this.useBasicAuthorization = useBasicAuthorization;
return this;
}
public boolean isUseBasicAuthorization() {
return useBasicAuthorization;
}
public String getClientAssertionType() {
return clientAssertionType;
}
public OAuth2Options setClientAssertionType(String clientAssertionType) {
this.clientAssertionType = clientAssertionType;
return this;
}
public String getClientAssertion() {
return clientAssertion;
}
public OAuth2Options setClientAssertion(String clientAssertion) {
this.clientAssertion = clientAssertion;
return this;
}
/**
* The User-Agent header to use when communicating with a provider
*
* @return the user agent string
*/
public String getUserAgent() {
return userAgent;
}
/**
* Set a custom user agent to use when communicating to a provider
*
* @param userAgent the user agent
* @return self
*/
public OAuth2Options setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
/**
* Custom headers to send along with every request.
*
* @return the headers as a json structure
*/
public JsonObject getHeaders() {
return headers;
}
/**
* Set custom headers to be sent with every request to the provider
*
* @param headers the headers
* @return self
*/
public OAuth2Options setHeaders(JsonObject headers) {
this.headers = headers;
return this;
}
/**
* The provider PubSec key options
*
* @return the pub sec key options
*/
public List getPubSecKeys() {
return pubSecKeys;
}
public OAuth2Options setPubSecKeys(List pubSecKeys) {
this.pubSecKeys = pubSecKeys;
return this;
}
public OAuth2Options addPubSecKey(PubSecKeyOptions pubSecKey) {
if (pubSecKeys == null) {
pubSecKeys = new ArrayList<>();
}
pubSecKeys.add(pubSecKey);
return this;
}
/**
* The provider logout path
*
* @return a logout resource path
*/
public String getLogoutPath() {
return logoutPath;
}
/**
* Set the provider logout path
*
* @param logoutPath a logout resource path
* @return self
*/
public OAuth2Options setLogoutPath(String logoutPath) {
this.logoutPath = logoutPath;
return this;
}
/**
* The provider userInfo resource path
*
* @return a resouce path
*/
public String getUserInfoPath() {
return userInfoPath;
}
/**
* Set the provider userInfo resource path
*
* @param userInfoPath a resource path
* @return self
*/
public OAuth2Options setUserInfoPath(String userInfoPath) {
this.userInfoPath = userInfoPath;
return this;
}
/**
* Set the provider scope separator
*
* @return a single character string usually a space or a plus
*/
public String getScopeSeparator() {
return scopeSeparator;
}
/**
* Set the provider scope separator
*
* @param scopeSeparator a separator e.g.: ' ', '+', ','
* @return self
*/
public OAuth2Options setScopeSeparator(String scopeSeparator) {
this.scopeSeparator = scopeSeparator;
return this;
}
/**
* Extra parameters to send to the provider
*
* @return a json representation of the parameters
*/
public JsonObject getExtraParameters() {
return extraParams;
}
/**
* Set extra parameters to be sent to the provider on each request
*
* @param extraParams a json representation of the parameters
* @return self
*/
public OAuth2Options setExtraParameters(JsonObject extraParams) {
this.extraParams = extraParams;
return this;
}
/**
* The provider token introspection resource path
*
* @return the resource path
*/
public String getIntrospectionPath() {
return introspectionPath;
}
/**
* Set the provider token introspection resource path
*
* @param introspectionPath a resource path
* @return self
*/
public OAuth2Options setIntrospectionPath(String introspectionPath) {
this.introspectionPath = introspectionPath;
return this;
}
/**
* Set the provider custom userInfo parameters to send when requesting them.
*
* @return a json representation of the extra parameters
*/
public JsonObject getUserInfoParameters() {
return userInfoParams;
}
/**
* Set custom parameters to be sent during the userInfo resource request
*
* @param userInfoParams json representation of the parameters
* @return self
*/
public OAuth2Options setUserInfoParameters(JsonObject userInfoParams) {
this.userInfoParams = userInfoParams;
return this;
}
public String getJwkPath() {
return jwkPath;
}
public OAuth2Options setJwkPath(String jwkPath) {
this.jwkPath = jwkPath;
return this;
}
public JWTOptions getJWTOptions() {
return jwtOptions;
}
public OAuth2Options setJWTOptions(JWTOptions jwtOptions) {
this.jwtOptions = jwtOptions;
return this;
}
public boolean isValidateIssuer() {
return validateIssuer;
}
public OAuth2Options setValidateIssuer(boolean validateIssuer) {
this.validateIssuer = validateIssuer;
return this;
}
public String getTenant() {
return tenant;
}
/**
* Sets an optional tenant. Tenants are used in some OpenID servers as placeholders for the URLs.
* The tenant should be set prior to any URL as it affects the way the URLs will be stored.
*
* Some provders may name this differently, for example: `realm`.
*
* @param tenant the tenant/realm for this config.
* @return self
*/
public OAuth2Options setTenant(String tenant) {
this.tenant = tenant;
return this;
}
/**
* The provider supported grant types
*
* @return the supported grant types options
*/
public List getSupportedGrantTypes() {
return supportedGrantTypes;
}
public OAuth2Options setSupportedGrantTypes(List supportedGrantTypes) {
this.supportedGrantTypes = supportedGrantTypes;
return this;
}
public OAuth2Options addSupportedGrantType(String supportedGrantType) {
if (supportedGrantTypes == null) {
supportedGrantTypes = new ArrayList<>();
}
supportedGrantTypes.add(supportedGrantType);
return this;
}
public void replaceVariables(boolean strict) {
// strip trailing slashes if present
if (site != null && site.endsWith("/")) {
site = site.substring(0, site.length() - 1);
}
site = replaceVariables(site);
authorizationPath = replaceVariables(authorizationPath);
tokenPath = replaceVariables(tokenPath);
revocationPath = replaceVariables(revocationPath);
logoutPath = replaceVariables(logoutPath);
userInfoPath = replaceVariables(userInfoPath);
introspectionPath = replaceVariables(introspectionPath);
jwkPath = replaceVariables(jwkPath);
if (extraParams != null) {
for (Map.Entry kv : extraParams) {
Object v = kv.getValue();
if (v instanceof String) {
try {
kv.setValue(replaceVariables((String) v));
} catch (IllegalStateException e) {
// if we're strict the we assert that even the optional extra parameters must
// be updated with the variable value
if (strict) {
throw e;
}
}
}
}
}
}
private static final Pattern TENANT_PATTER = Pattern.compile("\\{(tenant|realm)}");
private String replaceVariables(String path) {
if (path != null) {
final Matcher matcher = TENANT_PATTER.matcher(path);
if (matcher.find()) {
if (tenant == null) {
throw new IllegalStateException("Configuration with placeholders require that \"tenant\" is prior set");
}
return matcher.replaceAll(tenant);
}
}
return path;
}
public void validate() throws IllegalStateException {
List supportedGrantTypes = getSupportedGrantTypes();
if (supportedGrantTypes == null) {
// we default to AUTH_CODE and IMPLICIT as defined in the OpenID Connect spec
supportedGrantTypes = Arrays.asList(OAuth2FlowType.AUTH_CODE.getGrantType(), OAuth2FlowType.IMPLICIT.getGrantType());
}
for (OAuth2FlowType flow : OAuth2FlowType.values()) {
if (!supportedGrantTypes.contains(flow.getGrantType())) {
continue;
}
switch (flow) {
case AUTH_CODE:
case AUTH_JWT:
case AAD_OBO:
if (clientAssertion == null && clientAssertionType == null) {
// not using client assertions
if (clientId == null) {
throw new IllegalStateException("Configuration missing. You need to specify [clientId]");
}
} else {
if (clientAssertion == null || clientAssertionType == null) {
throw new IllegalStateException(
"Configuration missing. You need to specify [clientAssertion] AND [clientAssertionType]");
}
}
break;
case PASSWORD:
if (clientAssertion == null && clientAssertionType == null) {
// not using client assertions
if (clientId == null) {
LOG.debug("If you are using Client Oauth2 Resource Owner flow. You need to specify [clientId]");
}
} else {
if (clientAssertion == null || clientAssertionType == null) {
throw new IllegalStateException(
"Configuration missing. You need to specify [clientAssertion] AND [clientAssertionType]");
}
}
break;
}
}
}
public JsonObject toJson() {
final JsonObject json = new JsonObject();
OAuth2OptionsConverter.toJson(this, json);
return json;
}
@Override
public String toString() {
return toJson().encode();
}
public HttpClientOptions getHttpClientOptions() {
return httpClientOptions;
}
public OAuth2Options setHttpClientOptions(HttpClientOptions httpClientOptions) {
this.httpClientOptions = httpClientOptions;
return this;
}
public long getJwkMaxAgeInSeconds() {
return jwkMaxAge;
}
/**
* -1 means no rotation for JWKs
*
* @param jwkMaxAgeInSeconds timeout of JWKs rotation
*/
public void setJwkMaxAgeInSeconds(long jwkMaxAgeInSeconds) {
this.jwkMaxAge = jwkMaxAgeInSeconds;
}
public List getJwks() {
return jwks;
}
/**
* Sets the initial local JWKs
* @param jwks a json array as defined in https://tools.ietf.org/html/rfc7517#section-5
* @return self
*/
@Fluent
public OAuth2Options setJwks(List jwks) {
this.jwks = jwks;
return this;
}
/**
* Adds a local JWKs
* @param jwk a single keyas defined in https://tools.ietf.org/html/rfc7517#section-5
* @return self
*/
@Fluent
public OAuth2Options addJwk(JsonObject jwk) {
if (this.jwks == null) {
this.jwks = new ArrayList<>();
}
this.jwks.add(jwk);
return this;
}
}