From 236dbd01a029b2fdc4467e0c3e41647d1815a3f5 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Tue, 31 Aug 2021 09:44:58 +0200 Subject: [PATCH] OpenAPI Auto security based on extensions Signed-off-by: Phillip Kruger --- .../oidc/deployment/OidcBuildStep.java | 9 ++ .../jwt/deployment/SmallRyeJwtProcessor.java | 6 ++ .../deployment/SmallRyeOpenApiConfig.java | 6 ++ .../deployment/SmallRyeOpenApiProcessor.java | 77 ++++++++++++++++- .../runtime/OpenApiDocumentService.java | 26 +++--- .../openapi/runtime/OpenApiHandler.java | 7 +- .../openapi/runtime/OpenApiRecorder.java | 6 +- .../filter/AutoBasicSecurityFilter.java | 37 +++++++++ .../runtime/filter/AutoJWTSecurityFilter.java | 48 +++++++++++ .../runtime/filter/AutoSecurityFilter.java | 82 +++++++++++++++++++ .../openapi/runtime/filter/AutoUrl.java | 60 ++++++++++++++ .../filter/OpenIDConnectSecurityFilter.java | 71 ++++++++++++++++ .../deployment/HttpSecurityProcessor.java | 8 +- .../SecurityInformationBuildItem.java | 59 +++++++++++++ 14 files changed, 486 insertions(+), 16 deletions(-) create mode 100644 extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoBasicSecurityFilter.java create mode 100644 extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoJWTSecurityFilter.java create mode 100644 extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoSecurityFilter.java create mode 100644 extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoUrl.java create mode 100644 extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/OpenIDConnectSecurityFilter.java create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/SecurityInformationBuildItem.java diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java index b0b958807c0da..fad473c1b05b7 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/OidcBuildStep.java @@ -38,6 +38,7 @@ import io.quarkus.oidc.runtime.TenantConfigBean; import io.quarkus.runtime.TlsConfig; import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; +import io.quarkus.vertx.http.deployment.SecurityInformationBuildItem; import io.smallrye.jwt.auth.cdi.ClaimValueProducer; import io.smallrye.jwt.auth.cdi.CommonJwtProducer; import io.smallrye.jwt.auth.cdi.JsonValueProducer; @@ -51,6 +52,14 @@ FeatureBuildItem featureBuildItem() { return new FeatureBuildItem(Feature.OIDC); } + @BuildStep(onlyIf = IsEnabled.class) + public void provideSecurityInformation(BuildProducer securityInformationProducer) { + // TODO: By default quarkus.oidc.application-type = service + // Also look at other options (web-app, hybrid) + securityInformationProducer + .produce(SecurityInformationBuildItem.OPENIDCONNECT("quarkus.oidc.auth-server-url")); + } + @BuildStep(onlyIf = IsEnabled.class) AdditionalBeanBuildItem jwtClaimIntegration(Capabilities capabilities) { if (!capabilities.isPresent(Capability.JWT)) { diff --git a/extensions/smallrye-jwt/deployment/src/main/java/io/quarkus/smallrye/jwt/deployment/SmallRyeJwtProcessor.java b/extensions/smallrye-jwt/deployment/src/main/java/io/quarkus/smallrye/jwt/deployment/SmallRyeJwtProcessor.java index a4aeafe88a981..3fe6668cc1282 100644 --- a/extensions/smallrye-jwt/deployment/src/main/java/io/quarkus/smallrye/jwt/deployment/SmallRyeJwtProcessor.java +++ b/extensions/smallrye-jwt/deployment/src/main/java/io/quarkus/smallrye/jwt/deployment/SmallRyeJwtProcessor.java @@ -37,6 +37,7 @@ import io.quarkus.smallrye.jwt.runtime.auth.JwtPrincipalProducer; import io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator; import io.quarkus.smallrye.jwt.runtime.auth.RawOptionalClaimCreator; +import io.quarkus.vertx.http.deployment.SecurityInformationBuildItem; import io.smallrye.jwt.algorithm.KeyEncryptionAlgorithm; import io.smallrye.jwt.algorithm.SignatureAlgorithm; import io.smallrye.jwt.auth.cdi.ClaimValueProducer; @@ -69,6 +70,11 @@ EnableAllSecurityServicesBuildItem security() { return new EnableAllSecurityServicesBuildItem(); } + @BuildStep(onlyIf = IsEnabled.class) + public void provideSecurityInformation(BuildProducer securityInformationProducer) { + securityInformationProducer.produce(SecurityInformationBuildItem.JWT()); + } + /** * Register the CDI beans that are needed by the MP-JWT extension * diff --git a/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java index ee2c9295eb583..22a8546f728d4 100644 --- a/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java +++ b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java @@ -70,6 +70,12 @@ public final class SmallRyeOpenApiConfig { @ConfigItem(defaultValue = "true") public boolean autoAddTags; + /** + * This will automatically add security based on the security extension included (if any). + */ + @ConfigItem(defaultValue = "true") + public boolean autoAddSecurity; + /** * Add a scheme value to the Basic HTTP Security Scheme */ diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java index 3689aef33ab9e..b8c40f5650d6d 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java @@ -76,9 +76,14 @@ import io.quarkus.smallrye.openapi.runtime.OpenApiDocumentService; import io.quarkus.smallrye.openapi.runtime.OpenApiRecorder; import io.quarkus.smallrye.openapi.runtime.OpenApiRuntimeConfig; +import io.quarkus.smallrye.openapi.runtime.filter.AutoBasicSecurityFilter; +import io.quarkus.smallrye.openapi.runtime.filter.AutoJWTSecurityFilter; +import io.quarkus.smallrye.openapi.runtime.filter.AutoUrl; +import io.quarkus.smallrye.openapi.runtime.filter.OpenIDConnectSecurityFilter; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; +import io.quarkus.vertx.http.deployment.SecurityInformationBuildItem; import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem; import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.smallrye.openapi.api.OpenApiConfig; @@ -168,9 +173,11 @@ RouteBuildItem handler(LaunchModeBuildItem launch, BuildProducer displayableEndpoints, OpenApiRecorder recorder, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, + List securityInformationBuildItems, OpenApiRuntimeConfig openApiRuntimeConfig, ShutdownContextBuildItem shutdownContext, SmallRyeOpenApiConfig openApiConfig, + OpenApiFilteredIndexViewBuildItem apiFilteredIndexViewBuildItem, HttpConfiguration httpConfiguration) { /* * Ugly Hack @@ -187,7 +194,17 @@ RouteBuildItem handler(LaunchModeBuildItem launch, recorder.setupClDevMode(shutdownContext); } - Handler handler = recorder.handler(openApiRuntimeConfig, httpConfiguration); + OASFilter autoSecurityFilter = null; + if (openApiConfig.autoAddSecurity) { + // Only add the security if there are secured endpoints + OASFilter autoRolesAllowedFilter = getAutoRolesAllowedFilter(openApiConfig.securitySchemeName, + apiFilteredIndexViewBuildItem, openApiConfig); + if (autoRolesAllowedFilter != null) { + autoSecurityFilter = getAutoSecurityFilter(securityInformationBuildItems, openApiConfig); + } + } + + Handler handler = recorder.handler(openApiRuntimeConfig, httpConfiguration, autoSecurityFilter); return nonApplicationRootPathBuildItem.routeBuilder() .route(openApiConfig.path) .routeConfigKey("quarkus.smallrye-openapi.path") @@ -277,6 +294,64 @@ void addSecurityFilter(BuildProducer addToOpenA } + private OASFilter getAutoSecurityFilter(List securityInformationBuildItems, + SmallRyeOpenApiConfig config) { + + // Auto add a security from security extension(s) + if (!config.securityScheme.isPresent() && securityInformationBuildItems != null + && !securityInformationBuildItems.isEmpty()) { + // This needs to be a filter in runtime as the config we use to auto configure is in runtime + for (SecurityInformationBuildItem securityInformationBuildItem : securityInformationBuildItems) { + SecurityInformationBuildItem.SecurityModel securityModel = securityInformationBuildItem.getSecurityModel(); + switch (securityModel) { + case jwt: + return new AutoJWTSecurityFilter( + config.securitySchemeName, + config.securitySchemeDescription, + config.jwtSecuritySchemeValue, + config.jwtBearerFormat); + case basic: + return new AutoBasicSecurityFilter( + config.securitySchemeName, + config.securitySchemeDescription, + config.basicSecuritySchemeValue); + case oidc: + Optional maybeInfo = securityInformationBuildItem + .getOpenIDConnectInformation(); + + if (maybeInfo.isPresent()) { + SecurityInformationBuildItem.OpenIDConnectInformation info = maybeInfo.get(); + + AutoUrl authorizationUrl = new AutoUrl( + config.oidcOpenIdConnectUrl.orElse(null), + info.getUrlConfigKey(), + "/protocol/openid-connect/auth"); + + AutoUrl refreshUrl = new AutoUrl( + config.oidcOpenIdConnectUrl.orElse(null), + info.getUrlConfigKey(), + "/protocol/openid-connect/token"); + + AutoUrl tokenUrl = new AutoUrl( + config.oidcOpenIdConnectUrl.orElse(null), + info.getUrlConfigKey(), + "/protocol/openid-connect/token/introspect"); + + return new OpenIDConnectSecurityFilter( + config.securitySchemeName, + config.securitySchemeDescription, + authorizationUrl, refreshUrl, tokenUrl); + + } + break; + default: + break; + } + } + } + return null; + } + private OASFilter getAutoRolesAllowedFilter(String securitySchemeName, OpenApiFilteredIndexViewBuildItem apiFilteredIndexViewBuildItem, SmallRyeOpenApiConfig config) { diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentService.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentService.java index c9625b408becf..95f19a6f1f1bf 100644 --- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentService.java +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiDocumentService.java @@ -4,7 +4,6 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; -import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.config.Config; @@ -30,16 +29,15 @@ public class OpenApiDocumentService implements OpenApiDocumentHolder { private OpenApiDocumentHolder documentHolder; - @PostConstruct - void create() throws IOException { + void init(OASFilter autoSecurityFilter) { Config config = ConfigProvider.getConfig(); this.alwaysRunFilter = config.getOptionalValue("quarkus.smallrye-openapi.always-run-filter", Boolean.class) .orElse(Boolean.FALSE); if (alwaysRunFilter) { - this.documentHolder = new DynamicDocument(config); + this.documentHolder = new DynamicDocument(config, autoSecurityFilter); } else { - this.documentHolder = new StaticDocument(config); + this.documentHolder = new StaticDocument(config, autoSecurityFilter); } } @@ -59,7 +57,7 @@ class StaticDocument implements OpenApiDocumentHolder { private byte[] jsonDocument; private byte[] yamlDocument; - StaticDocument(Config config) { + StaticDocument(Config config, OASFilter autoFilter) { ClassLoader cl = OpenApiConstants.classLoader == null ? Thread.currentThread().getContextClassLoader() : OpenApiConstants.classLoader; try (InputStream is = cl.getResourceAsStream(OpenApiConstants.BASE_NAME + Format.JSON)) { @@ -72,6 +70,9 @@ class StaticDocument implements OpenApiDocumentHolder { document.reset(); document.config(openApiConfig); document.modelFromStaticFile(OpenApiProcessor.modelFromStaticFile(staticFile)); + if (autoFilter != null) { + document.filter(autoFilter); + } document.filter(OpenApiProcessor.getFilter(openApiConfig, cl)); document.initialize(); @@ -104,9 +105,10 @@ class DynamicDocument implements OpenApiDocumentHolder { private OpenAPI generatedOnBuild; private OpenApiConfig openApiConfig; - private OASFilter filter; + private OASFilter userFilter; + private OASFilter autoFilter; - DynamicDocument(Config config) { + DynamicDocument(Config config, OASFilter autoFilter) { ClassLoader cl = OpenApiConstants.classLoader == null ? Thread.currentThread().getContextClassLoader() : OpenApiConstants.classLoader; try (InputStream is = cl.getResourceAsStream(OpenApiConstants.BASE_NAME + Format.JSON)) { @@ -114,7 +116,8 @@ class DynamicDocument implements OpenApiDocumentHolder { try (OpenApiStaticFile staticFile = new OpenApiStaticFile(is, Format.JSON)) { this.generatedOnBuild = OpenApiProcessor.modelFromStaticFile(staticFile); this.openApiConfig = new OpenApiConfigImpl(config); - this.filter = OpenApiProcessor.getFilter(openApiConfig, cl); + this.userFilter = OpenApiProcessor.getFilter(openApiConfig, cl); + this.autoFilter = autoFilter; } } } catch (IOException ex) { @@ -153,7 +156,10 @@ private OpenApiDocument getOpenApiDocument() { document.reset(); document.config(this.openApiConfig); document.modelFromStaticFile(this.generatedOnBuild); - document.filter(this.filter); + if (this.autoFilter != null) { + document.filter(this.autoFilter); + } + document.filter(this.userFilter); document.initialize(); return document; } diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiHandler.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiHandler.java index 9be329c3afea7..c226b42b47fc3 100644 --- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiHandler.java +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiHandler.java @@ -2,6 +2,8 @@ import java.util.List; +import org.eclipse.microprofile.openapi.OASFilter; + import io.quarkus.arc.Arc; import io.smallrye.openapi.runtime.io.Format; import io.vertx.core.Handler; @@ -32,9 +34,11 @@ public class OpenApiHandler implements Handler { } final boolean corsEnabled; + final OASFilter autoSecurityFilter; - public OpenApiHandler(boolean corsEnabled) { + public OpenApiHandler(boolean corsEnabled, OASFilter autoSecurityFilter) { this.corsEnabled = corsEnabled; + this.autoSecurityFilter = autoSecurityFilter; } @Override @@ -77,6 +81,7 @@ public void handle(RoutingContext event) { private OpenApiDocumentService getOpenApiDocumentService() { if (this.openApiDocumentService == null) { this.openApiDocumentService = Arc.container().instance(OpenApiDocumentService.class).get(); + this.openApiDocumentService.init(this.autoSecurityFilter); } return this.openApiDocumentService; } diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java index be5250c3b0075..3d3e3c196ee28 100644 --- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiRecorder.java @@ -5,6 +5,7 @@ import java.net.URL; import java.util.Enumeration; +import org.eclipse.microprofile.openapi.OASFilter; import org.eclipse.microprofile.openapi.spi.OASFactoryResolver; import io.quarkus.runtime.ShutdownContext; @@ -16,9 +17,10 @@ @Recorder public class OpenApiRecorder { - public Handler handler(OpenApiRuntimeConfig runtimeConfig, HttpConfiguration configuration) { + public Handler handler(OpenApiRuntimeConfig runtimeConfig, HttpConfiguration configuration, + OASFilter autoSecurityFilter) { if (runtimeConfig.enable) { - return new OpenApiHandler(configuration.corsEnabled); + return new OpenApiHandler(configuration.corsEnabled, autoSecurityFilter); } else { return new OpenApiNotFoundHandler(); } diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoBasicSecurityFilter.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoBasicSecurityFilter.java new file mode 100644 index 0000000000000..fc1850052c050 --- /dev/null +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoBasicSecurityFilter.java @@ -0,0 +1,37 @@ +package io.quarkus.smallrye.openapi.runtime.filter; + +import org.eclipse.microprofile.openapi.OASFactory; +import org.eclipse.microprofile.openapi.models.security.SecurityScheme; + +/** + * Add Basic Authentication security automatically based on the added security extensions + */ +public class AutoBasicSecurityFilter extends AutoSecurityFilter { + private String basicSecuritySchemeValue; + + public AutoBasicSecurityFilter() { + super(); + } + + public AutoBasicSecurityFilter(String securitySchemeName, String securitySchemeDescription, + String basicSecuritySchemeValue) { + super(securitySchemeName, securitySchemeDescription); + this.basicSecuritySchemeValue = basicSecuritySchemeValue; + } + + public String getBasicSecuritySchemeValue() { + return basicSecuritySchemeValue; + } + + public void setBasicSecuritySchemeValue(String basicSecuritySchemeValue) { + this.basicSecuritySchemeValue = basicSecuritySchemeValue; + } + + @Override + protected SecurityScheme getSecurityScheme() { + SecurityScheme securityScheme = OASFactory.createSecurityScheme(); + securityScheme.setType(SecurityScheme.Type.HTTP); + securityScheme.setScheme(basicSecuritySchemeValue); + return securityScheme; + } +} \ No newline at end of file diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoJWTSecurityFilter.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoJWTSecurityFilter.java new file mode 100644 index 0000000000000..c246fce55b844 --- /dev/null +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoJWTSecurityFilter.java @@ -0,0 +1,48 @@ +package io.quarkus.smallrye.openapi.runtime.filter; + +import org.eclipse.microprofile.openapi.OASFactory; +import org.eclipse.microprofile.openapi.models.security.SecurityScheme; + +/** + * Add JWT Authentication security automatically based on the added security extensions + */ +public class AutoJWTSecurityFilter extends AutoSecurityFilter { + private String jwtSecuritySchemeValue; + private String jwtBearerFormat; + + public AutoJWTSecurityFilter() { + super(); + } + + public AutoJWTSecurityFilter(String securitySchemeName, String securitySchemeDescription, String jwtSecuritySchemeValue, + String jwtBearerFormat) { + super(securitySchemeName, securitySchemeDescription); + this.jwtSecuritySchemeValue = jwtSecuritySchemeValue; + this.jwtBearerFormat = jwtBearerFormat; + } + + public String getJwtSecuritySchemeValue() { + return jwtSecuritySchemeValue; + } + + public void setJwtSecuritySchemeValue(String jwtSecuritySchemeValue) { + this.jwtSecuritySchemeValue = jwtSecuritySchemeValue; + } + + public String getJwtBearerFormat() { + return jwtBearerFormat; + } + + public void setJwtBearerFormat(String jwtBearerFormat) { + this.jwtBearerFormat = jwtBearerFormat; + } + + @Override + protected SecurityScheme getSecurityScheme() { + SecurityScheme securityScheme = OASFactory.createSecurityScheme(); + securityScheme.setType(SecurityScheme.Type.HTTP); + securityScheme.setScheme(jwtSecuritySchemeValue); + securityScheme.setBearerFormat(jwtBearerFormat); + return securityScheme; + } +} diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoSecurityFilter.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoSecurityFilter.java new file mode 100644 index 0000000000000..00f4cd8c597cf --- /dev/null +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoSecurityFilter.java @@ -0,0 +1,82 @@ +package io.quarkus.smallrye.openapi.runtime.filter; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.openapi.OASFactory; +import org.eclipse.microprofile.openapi.OASFilter; +import org.eclipse.microprofile.openapi.models.OpenAPI; +import org.eclipse.microprofile.openapi.models.security.SecurityScheme; +import org.jboss.logging.Logger; + +/** + * Auto add security + */ +public abstract class AutoSecurityFilter implements OASFilter { + private static final Logger log = Logger.getLogger(AutoSecurityFilter.class); + + private String securitySchemeName; + private String securitySchemeDescription; + + public AutoSecurityFilter() { + + } + + public AutoSecurityFilter(String securitySchemeName, String securitySchemeDescription) { + this.securitySchemeName = securitySchemeName; + this.securitySchemeDescription = securitySchemeDescription; + } + + public String getSecuritySchemeName() { + return securitySchemeName; + } + + public void setSecuritySchemeName(String securitySchemeName) { + this.securitySchemeName = securitySchemeName; + } + + public String getSecuritySchemeDescription() { + return securitySchemeDescription; + } + + public void setSecuritySchemeDescription(String securitySchemeDescription) { + this.securitySchemeDescription = securitySchemeDescription; + } + + @Override + public void filterOpenAPI(OpenAPI openAPI) { + // Make sure components are created + if (openAPI.getComponents() == null) { + openAPI.setComponents(OASFactory.createComponents()); + } + + Map securitySchemes = new HashMap<>(); + + // Add any existing security + if (openAPI.getComponents().getSecuritySchemes() != null + && !openAPI.getComponents().getSecuritySchemes().isEmpty()) { + securitySchemes.putAll(openAPI.getComponents().getSecuritySchemes()); + } + + SecurityScheme securityScheme = getSecurityScheme(); + securityScheme.setDescription(securitySchemeDescription); + securitySchemes.put(securitySchemeName, securityScheme); + openAPI.getComponents().setSecuritySchemes(securitySchemes); + } + + protected abstract SecurityScheme getSecurityScheme(); + + protected String getUrl(String configKey, String defaultValue, String shouldEndWith) { + Config c = ConfigProvider.getConfig(); + + String u = c.getOptionalValue(configKey, String.class).orElse(defaultValue); + + if (u != null && !u.endsWith(shouldEndWith)) { + u = u + shouldEndWith; + } + return u; + } + +} \ No newline at end of file diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoUrl.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoUrl.java new file mode 100644 index 0000000000000..8ee97b65c624d --- /dev/null +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/AutoUrl.java @@ -0,0 +1,60 @@ +package io.quarkus.smallrye.openapi.runtime.filter; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; + +/** + * Create a URL from a config, or a default value + */ +public class AutoUrl { + + private String defaultValue; + private String configKey; + private String path; + + public AutoUrl() { + } + + public AutoUrl(String defaultValue, String configKey, String path) { + this.defaultValue = defaultValue; + this.configKey = configKey; + this.path = path; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public String getConfigKey() { + return configKey; + } + + public void setConfigKey(String configKey) { + this.configKey = configKey; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getFinalUrlValue() { + Config c = ConfigProvider.getConfig(); + + String u = c.getOptionalValue(this.configKey, String.class).orElse(defaultValue); + + if (u != null && path != null && !u.endsWith(path)) { + u = u + path; + } + + return u; + } + +} diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/OpenIDConnectSecurityFilter.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/OpenIDConnectSecurityFilter.java new file mode 100644 index 0000000000000..cbc18a5c320b7 --- /dev/null +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/filter/OpenIDConnectSecurityFilter.java @@ -0,0 +1,71 @@ +package io.quarkus.smallrye.openapi.runtime.filter; + +import org.eclipse.microprofile.openapi.OASFactory; +import org.eclipse.microprofile.openapi.models.security.OAuthFlow; +import org.eclipse.microprofile.openapi.models.security.OAuthFlows; +import org.eclipse.microprofile.openapi.models.security.SecurityScheme; + +/** + * Add OAuth 2 (Implicit) Authentication security automatically based on the added security extensions + */ +public class OpenIDConnectSecurityFilter extends AutoSecurityFilter { + + private AutoUrl authorizationUrl; + private AutoUrl refreshUrl; + private AutoUrl tokenUrl; + + public OpenIDConnectSecurityFilter() { + super(); + } + + public OpenIDConnectSecurityFilter(String securitySchemeName, String securitySchemeDescription, + AutoUrl authorizationUrl, + AutoUrl refreshUrl, + AutoUrl tokenUrl) { + super(securitySchemeName, securitySchemeDescription); + this.authorizationUrl = authorizationUrl; + this.refreshUrl = refreshUrl; + this.tokenUrl = tokenUrl; + } + + public AutoUrl getAuthorizationUrl() { + return authorizationUrl; + } + + public void setAuthorizationUrl(AutoUrl authorizationUrl) { + this.authorizationUrl = authorizationUrl; + } + + public AutoUrl getRefreshUrl() { + return refreshUrl; + } + + public void setRefreshUrl(AutoUrl refreshUrl) { + this.refreshUrl = refreshUrl; + } + + public AutoUrl getTokenUrl() { + return tokenUrl; + } + + public void setTokenUrl(AutoUrl tokenUrl) { + this.tokenUrl = tokenUrl; + } + + @Override + protected SecurityScheme getSecurityScheme() { + SecurityScheme securityScheme = OASFactory.createSecurityScheme(); + + securityScheme.setType(SecurityScheme.Type.OAUTH2); + OAuthFlows oAuthFlows = OASFactory.createOAuthFlows(); + OAuthFlow oAuthFlow = OASFactory.createOAuthFlow(); + oAuthFlow.authorizationUrl(this.authorizationUrl.getFinalUrlValue()); + oAuthFlow.refreshUrl(this.refreshUrl.getFinalUrlValue()); + oAuthFlow.tokenUrl(this.tokenUrl.getFinalUrlValue()); + oAuthFlows.setImplicit(oAuthFlow); + securityScheme.setFlows(oAuthFlows); + + return securityScheme; + } + +} \ No newline at end of file diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java index e6a7e0d54c6c5..a7b7956bf0068 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/HttpSecurityProcessor.java @@ -89,7 +89,8 @@ SyntheticBeanBuildItem initMtlsClientAuth( @Record(ExecutionTime.RUNTIME_INIT) SyntheticBeanBuildItem initBasicAuth( HttpSecurityRecorder recorder, - HttpBuildTimeConfig buildTimeConfig) { + HttpBuildTimeConfig buildTimeConfig, + BuildProducer securityInformationProducer) { if ((buildTimeConfig.auth.form.enabled || isMtlsClientAuthenticationEnabled(buildTimeConfig)) && !buildTimeConfig.auth.basic) { //if form auth is enabled and we are not then we don't install @@ -105,6 +106,7 @@ SyntheticBeanBuildItem initBasicAuth( && !buildTimeConfig.auth.basic) { //if not explicitly enabled we make this a default bean, so it is the fallback if nothing else is defined configurator.defaultBean(); + securityInformationProducer.produce(SecurityInformationBuildItem.BASIC()); } return configurator.done(); @@ -119,7 +121,8 @@ void setupAuthenticationMechanisms( Capabilities capabilities, BuildProducer beanContainerListenerBuildItemBuildProducer, HttpBuildTimeConfig buildTimeConfig, - List httpSecurityPolicyBuildItemList) { + List httpSecurityPolicyBuildItemList, + BuildProducer securityInformationProducer) { Map> policyMap = new HashMap<>(); for (HttpSecurityPolicyBuildItem e : httpSecurityPolicyBuildItemList) { if (policyMap.containsKey(e.getName())) { @@ -131,6 +134,7 @@ void setupAuthenticationMechanisms( if (buildTimeConfig.auth.form.enabled) { } else if (buildTimeConfig.auth.basic) { beanProducer.produce(AdditionalBeanBuildItem.unremovableOf(BasicAuthenticationMechanism.class)); + securityInformationProducer.produce(SecurityInformationBuildItem.BASIC()); } if (capabilities.isPresent(Capability.SECURITY)) { diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/SecurityInformationBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/SecurityInformationBuildItem.java new file mode 100644 index 0000000000000..f1b6c64aec8a4 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/SecurityInformationBuildItem.java @@ -0,0 +1,59 @@ +package io.quarkus.vertx.http.deployment; + +import java.util.Optional; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Contains information on the security model used in the application + */ +public final class SecurityInformationBuildItem extends MultiBuildItem { + + private final SecurityModel securityModel; + private final Optional openIDConnectInformation; + + public static SecurityInformationBuildItem BASIC() { + return new SecurityInformationBuildItem(SecurityModel.basic, Optional.empty()); + } + + public static SecurityInformationBuildItem JWT() { + return new SecurityInformationBuildItem(SecurityModel.jwt, Optional.empty()); + } + + public static SecurityInformationBuildItem OPENIDCONNECT(String urlConfigKey) { + return new SecurityInformationBuildItem(SecurityModel.oidc, + Optional.of(new OpenIDConnectInformation(urlConfigKey))); + } + + public SecurityInformationBuildItem(SecurityModel securityModel, + Optional openIDConnectInformation) { + this.securityModel = securityModel; + this.openIDConnectInformation = openIDConnectInformation; + } + + public SecurityModel getSecurityModel() { + return securityModel; + } + + public Optional getOpenIDConnectInformation() { + return openIDConnectInformation; + } + + public enum SecurityModel { + basic, + jwt, + oidc + } + + public static class OpenIDConnectInformation { + private final String urlConfigKey; + + public OpenIDConnectInformation(String urlConfigKey) { + this.urlConfigKey = urlConfigKey; + } + + public String getUrlConfigKey() { + return urlConfigKey; + } + } +} \ No newline at end of file