This is an automated email from the ASF dual-hosted git repository.
pvillard pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 250e57db61 NIFI-14852 Added Security Schemes to OpenAPI Specification
250e57db61 is described below
commit 250e57db611961f5172e7cb218cfbfbfac4fcf15
Author: exceptionfactory <[email protected]>
AuthorDate: Thu Aug 21 09:55:50 2025 -0500
NIFI-14852 Added Security Schemes to OpenAPI Specification
- Added StandardReader class to support handling unauthenticated methods
with empty SecurityRequirements annotation
- Added custom HTML template based on swagger-codegen version to control
Access description
Signed-off-by: Pierre Villard <[email protected]>
This closes #10228.
---
nifi-commons/nifi-swagger-integration/pom.xml | 9 +
.../nifi/swagger/integration/StandardReader.java | 96 ++++++++++
.../nifi-framework/nifi-web/nifi-web-api/pom.xml | 1 +
.../org/apache/nifi/web/api/AccessResource.java | 2 +
.../nifi/web/api/AuthenticationResource.java | 2 +
.../src/main/resources/openapi/openapi.yaml | 15 ++
.../src/main/resources/templates/index.mustache | 194 +++++++++++++++++++++
7 files changed, 319 insertions(+)
diff --git a/nifi-commons/nifi-swagger-integration/pom.xml
b/nifi-commons/nifi-swagger-integration/pom.xml
index b7a00822ff..fd29fbd8b1 100644
--- a/nifi-commons/nifi-swagger-integration/pom.xml
+++ b/nifi-commons/nifi-swagger-integration/pom.xml
@@ -29,6 +29,15 @@
<artifactId>swagger-integration-jakarta</artifactId>
<version>${swagger.annotations.version}</version>
</dependency>
+ <dependency>
+ <groupId>io.swagger.core.v3</groupId>
+ <artifactId>swagger-jaxrs2-jakarta</artifactId>
+ <version>${swagger.annotations.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.ws.rs</groupId>
+ <artifactId>jakarta.ws.rs-api</artifactId>
+ </dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
diff --git
a/nifi-commons/nifi-swagger-integration/src/main/java/org/apache/nifi/swagger/integration/StandardReader.java
b/nifi-commons/nifi-swagger-integration/src/main/java/org/apache/nifi/swagger/integration/StandardReader.java
new file mode 100644
index 0000000000..cc5ab89848
--- /dev/null
+++
b/nifi-commons/nifi-swagger-integration/src/main/java/org/apache/nifi/swagger/integration/StandardReader.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.swagger.integration;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
+import io.swagger.v3.jaxrs2.Reader;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
+import io.swagger.v3.oas.models.ExternalDocumentation;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.parameters.Parameter;
+import io.swagger.v3.oas.models.parameters.RequestBody;
+import io.swagger.v3.oas.models.responses.ApiResponses;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.servers.Server;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.Produces;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Standard JAX-RS Method Annotation Reader supporting custom Security
Requirement handling
+ */
+public class StandardReader extends Reader {
+
+ @Override
+ protected Operation parseMethod(
+ final Class<?> resourceClass,
+ final Method method,
+ final List<Parameter> parameters,
+ final Produces methodProduces,
+ final Produces classProduces,
+ final Consumes methodConsumes,
+ final Consumes classConsumes,
+ final List<SecurityRequirement> classSecurityRequirements,
+ final Optional<ExternalDocumentation> classExternalDocumentation,
+ final Set<String> classTags,
+ final List<Server> classServers,
+ final boolean isSubresource,
+ final RequestBody parentRequestBody,
+ final ApiResponses apiResponses,
+ final JsonView jsonViewAnnotation,
+ final ApiResponse[] classResponses,
+ final AnnotatedMethod annotatedMethod
+ ) {
+ final Operation operation = super.parseMethod(
+ resourceClass,
+ method,
+ parameters,
+ methodProduces,
+ classProduces,
+ methodConsumes,
+ classConsumes,
+ classSecurityRequirements,
+ classExternalDocumentation,
+ classTags,
+ classServers,
+ isSubresource,
+ parentRequestBody,
+ apiResponses,
+ jsonViewAnnotation,
+ classResponses,
+ annotatedMethod
+ );
+
+ // Search for empty SecurityRequirements annotation on method
+ final SecurityRequirements[] securityRequirements =
method.getAnnotationsByType(SecurityRequirements.class);
+ if (securityRequirements.length == 1) {
+ final SecurityRequirements requirements = securityRequirements[0];
+ if (requirements.value().length == 0) {
+ // Set empty Security element on Operation indicated no
authentication required
+ operation.setSecurity(List.of());
+ }
+ }
+
+ return operation;
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
index 939cb38606..bf5e93d6ba 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
+++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
@@ -117,6 +117,7 @@
<defaultResponseCode>200</defaultResponseCode>
<configurationFilePath>${project.build.outputDirectory}/openapi.yaml</configurationFilePath>
<objectMapperProcessorClass>org.apache.nifi.swagger.integration.StandardObjectMapperProcessor</objectMapperProcessorClass>
+
<readerClass>org.apache.nifi.swagger.integration.StandardReader</readerClass>
</configuration>
<executions>
<execution>
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
index 4dfbacd831..c2d6f33645 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessResource.java
@@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -104,6 +105,7 @@ public class AccessResource extends ApplicationResource {
@ApiResponse(responseCode = "500", description = "Unable
to create access token because an unexpected error occurred.")
}
)
+ @SecurityRequirements
public Response createAccessToken(
@Context final HttpServletRequest httpServletRequest,
@Context final HttpServletResponse httpServletResponse,
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AuthenticationResource.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AuthenticationResource.java
index fc40377964..1ceb2a8da9 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AuthenticationResource.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AuthenticationResource.java
@@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
@@ -70,6 +71,7 @@ public class AuthenticationResource extends
ApplicationResource {
summary = "Retrieves the authentication configuration endpoint and
status information",
responses = @ApiResponse(content = @Content(schema =
@Schema(implementation = AuthenticationConfigurationEntity.class)))
)
+ @SecurityRequirements
public Response getAuthenticationConfiguration() {
final AuthenticationConfigurationDTO configuration = new
AuthenticationConfigurationDTO();
configuration.setExternalLoginRequired(authenticationConfiguration.externalLoginRequired());
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/openapi/openapi.yaml
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/openapi/openapi.yaml
index fbed1e7591..b5c46fb07e 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/openapi/openapi.yaml
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/openapi/openapi.yaml
@@ -24,3 +24,18 @@ openAPI:
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
+ security:
+ - HTTPBearerJWT: []
+ - CookieSecureAuthorizationBearer: []
+ components:
+ securitySchemes:
+ HTTPBearerJWT:
+ description: HTTP Bearer authentication with JSON Web Token
+ type: http
+ scheme: Bearer
+ bearerFormat: JWT
+ CookieSecureAuthorizationBearer:
+ description: JSON Web Token authentication with session cookie
+ type: apiKey
+ in: cookie
+ name: __Secure-Authorization-Bearer
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/templates/index.mustache
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/templates/index.mustache
new file mode 100644
index 0000000000..86efa8b086
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/templates/index.mustache
@@ -0,0 +1,194 @@
+<!doctype html>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Original template derived from swagger-codegen htmlDocs
+-->
+<html>
+ <head>
+ <title>{{{appName}}}</title>
+ <style type="text/css">
+ {{>style.css}}
+ </style>
+ </head>
+ <body>
+ <h1>{{{appName}}}</h1>
+ <div class="app-desc">{{{appDescription}}}</div>
+ {{#infoUrl}}<div class="app-desc">More information: <a
href="{{{infoUrl}}}">{{{infoUrl}}}</a></div>{{/infoUrl}}
+ {{#infoEmail}}<div class="app-desc">Contact Info: <a
href="{{{infoEmail}}}">{{{infoEmail}}}</a></div>{{/infoEmail}}
+ {{#version}}<div class="app-desc">Version: {{{version}}}</div>{{/version}}
+ {{#basePathWithoutHost}}<div
class="app-desc">BasePath:{{basePathWithoutHost}}</div>{{/basePathWithoutHost}}
+ <div class="license-info">{{{licenseInfo}}}</div>
+ <div class="license-url">{{{licenseUrl}}}</div>
+ <h2>Access</h2>
+ {{#hasAuthMethods}}
+ <ol>
+ <li>HTTP Authorization header with Bearer scheme and JSON Web Token</li>
+ <li>HTTP session cookie named __Secure-Authorization-Bearer with JSON
Web Token</li>
+ </ol>
+ {{/hasAuthMethods}}
+
+ <h2><a name="__Methods">Methods</a></h2>
+ [ Jump to <a href="#__Models">Models</a> ]
+
+ <h3>Table of Contents </h3>
+ <div class="method-summary">{{access}}</div>
+ {{#apiInfo}}
+ {{#apis}}
+ {{#operations}}
+ <h4><a href="#{{baseName}}">{{baseName}}</a></h4>
+ <ul>
+ {{#operation}}
+ <li><a href="#{{nickname}}"><code><span
class="http-method">{{httpMethod}}</span> {{path}}</code></a></li>
+ {{/operation}}
+ </ul>
+ {{/operations}}
+ {{/apis}}
+ {{/apiInfo}}
+
+ {{#apiInfo}}
+ {{#apis}}
+ {{#operations}}
+ <h1><a name="{{baseName}}">{{baseName}}</a></h1>
+ {{#operation}}
+ <div class="method"><a name="{{nickname}}"/>
+ <div class="method-path">
+ <a class="up" href="#__Methods">Up</a>
+ <pre class="{{httpMethod}}"><code class="huge"><span
class="http-method">{{httpMethod}}</span> {{path}}</code></pre></div>
+ <div class="method-summary">{{summary}} (<span
class="nickname">{{nickname}}</span>)</div>
+ <div class="method-notes">{{notes}}</div>
+
+ {{#hasPathParams}}
+ <h3 class="field-label">Path parameters</h3>
+ <div class="field-items">
+ {{#pathParams}}{{>pathParam}}{{/pathParams}}
+ </div> <!-- field-items -->
+ {{/hasPathParams}}
+
+ {{#hasConsumes}}
+ <h3 class="field-label">Consumes</h3>
+ This API call consumes the following media types via the <span
class="header">Content-Type</span> request header:
+ <ul>
+ {{#consumes}}
+ <li><code>{{{mediaType}}}</code></li>
+ {{/consumes}}
+ </ul>
+ {{/hasConsumes}}
+
+ {{#hasBodyParam}}
+ <h3 class="field-label">Request body</h3>
+ <div class="field-items">
+ {{#bodyParams}}{{>bodyParam}}{{/bodyParams}}
+ </div> <!-- field-items -->
+ {{/hasBodyParam}}
+
+ {{#hasHeaderParams}}
+ <h3 class="field-label">Request headers</h3>
+ <div class="field-items">
+ {{#headerParams}}{{>headerParam}}{{/headerParams}}
+ </div> <!-- field-items -->
+ {{/hasHeaderParams}}
+
+ {{#hasQueryParams}}
+ <h3 class="field-label">Query parameters</h3>
+ <div class="field-items">
+ {{#queryParams}}{{>queryParam}}{{/queryParams}}
+ </div> <!-- field-items -->
+ {{/hasQueryParams}}
+
+ {{#hasFormParams}}
+ <h3 class="field-label">Form parameters</h3>
+ <div class="field-items">
+ {{#formParams}}{{>formParam}}{{/formParams}}
+ </div> <!-- field-items -->
+ {{/hasFormParams}}
+
+ {{#returnType}}
+ <h3 class="field-label">Return type</h3>
+ <div class="return-type">
+
{{#hasReference}}{{^returnSimpleType}}{{returnContainer}}[{{/returnSimpleType}}<a
href="#{{returnBaseType}}">{{returnBaseType}}</a>{{^returnSimpleType}}]{{/returnSimpleType}}{{/hasReference}}
+ {{^hasReference}}{{returnType}}{{/hasReference}}
+ </div>
+ {{/returnType}}
+
+ {{#hasExamples}}
+ {{#examples}}
+ <h3 class="field-label">Example data</h3>
+ <div class="example-data-content-type">Content-Type:
{{{contentType}}}</div>
+ <pre class="example"><code>{{{example}}}</code></pre>
+ {{/examples}}
+ {{/hasExamples}}
+
+ {{#hasProduces}}
+ <h3 class="field-label">Produces</h3>
+ This API call produces the following media types according to the <span
class="header">Accept</span> request header;
+ the media type will be conveyed by the <span
class="header">Content-Type</span> response header.
+ <ul>
+ {{#produces}}
+ <li><code>{{{mediaType}}}</code></li>
+ {{/produces}}
+ </ul>
+ {{/hasProduces}}
+
+ <h3 class="field-label">Responses</h3>
+ {{#responses}}
+ <h4 class="field-label">{{code}}</h4>
+ {{message}}
+ {{#simpleType}}<a href="#{{dataType}}">{{dataType}}</a>{{/simpleType}}
+ {{#examples}}
+ <h3 class="field-label">Example data</h3>
+ <div class="example-data-content-type">Content-Type:
{{{contentType}}}</div>
+ <pre class="example"><code>{{example}}</code></pre>
+ {{/examples}}
+ {{/responses}}
+ </div> <!-- method -->
+ <hr/>
+ {{/operation}}
+ {{/operations}}
+ {{/apis}}
+ {{/apiInfo}}
+
+ <h2><a name="__Models">Models</a></h2>
+ [ Jump to <a href="#__Methods">Methods</a> ]
+
+ <h3>Table of Contents</h3>
+ <ol>
+ {{#models}}
+ {{#model}}
+ <li><a href="#{{name}}"><code>{{name}}</code>{{#title}} -
{{title}}{{/title}}</a></li>
+ {{/model}}
+ {{/models}}
+ </ol>
+
+ {{#models}}
+ {{#model}}
+ <div class="model">
+ <h3><a name="{{name}}"><code>{{name}}</code>{{#title}} -
{{title}}{{/title}}</a> <a class="up" href="#__Models">Up</a></h3>
+ {{#unescapedDescription}}<div
class='model-description'>{{unescapedDescription}}</div>{{/unescapedDescription}}
+ <div class="field-items">
+ {{#vars}}<div class="param">{{name}}
{{^required}}(optional){{/required}}</div><div class="param-desc"><span
class="param-type">{{^isPrimitiveType}}<a
href="#{{complexType}}">{{datatype}}</a>{{/isPrimitiveType}}</span>
{{unescapedDescription}} {{#dataFormat}}format:
{{{dataFormat}}}{{/dataFormat}}</div>
+ {{#isEnum}}
+ <div class="param-enum-header">Enum:</div>
+ {{#_enum}}<div class="param-enum">{{this}}</div>{{/_enum}}
+ {{/isEnum}}
+ {{#example}}
+ <div class="param-desc"><span class="param-type">example:
{{example}}</span></div>
+ {{/example}}
+ {{/vars}}
+ </div> <!-- field-items -->
+ </div>
+ {{/model}}
+ {{/models}}
+ </body>
+</html>