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>

Reply via email to