From b1df259d3824c7de0b27feac684d5f5bd4577d6e Mon Sep 17 00:00:00 2001 From: Richard Whitehouse Date: Wed, 5 Jun 2019 16:08:05 +0000 Subject: [PATCH 1/3] [Core] Fix Codegen Operation Scope Consistency - Filter scopes based on operation - Partially revert #1984 to not rely on custom attributes as to whether scopes exist --- .../openapitools/codegen/CodegenSecurity.java | 46 ++++- .../codegen/DefaultGenerator.java | 77 ++++---- .../aspnetcore/2.1/controller.mustache | 12 +- .../resources/3_0/rust-server/openapi-v3.yaml | 25 +++ .../aspnetcore/.openapi-generator/VERSION | 2 +- .../Org.OpenAPITools/Controllers/PetApi.cs | 2 +- .../Org.OpenAPITools/Controllers/StoreApi.cs | 2 +- .../Org.OpenAPITools/Controllers/UserApi.cs | 2 +- .../src/Org.OpenAPITools/Models/Order.cs | 2 +- .../wwwroot/openapi-original.json | 4 +- .../rust-server/output/openapi-v3/README.md | 20 +- .../output/openapi-v3/api/openapi.yaml | 27 +++ .../output/openapi-v3/docs/default_api.md | 46 +++++ .../output/openapi-v3/examples/client.rs | 14 ++ .../openapi-v3/examples/server_lib/mod.rs | 3 +- .../openapi-v3/examples/server_lib/server.rs | 16 ++ .../output/openapi-v3/src/client/mod.rs | 132 ++++++++++++- .../rust-server/output/openapi-v3/src/lib.rs | 34 ++++ .../output/openapi-v3/src/server/context.rs | 10 + .../output/openapi-v3/src/server/mod.rs | 173 +++++++++++++++++- 20 files changed, 584 insertions(+), 65 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenSecurity.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenSecurity.java index ea3549712027..ea51d6ff5593 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenSecurity.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenSecurity.java @@ -17,6 +17,7 @@ package org.openapitools.codegen; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -38,7 +39,7 @@ public class CodegenSecurity { // Oauth specific public String flow, authorizationUrl, tokenUrl; public List> scopes; - public Boolean isCode, isPassword, isApplication, isImplicit, hasScopes; + public Boolean isCode, isPassword, isApplication, isImplicit; @Override public String toString() { @@ -100,4 +101,47 @@ public int hashCode() { isImplicit, scopes); } + + // Return a copy of the security object, filtering out any scopes from the passed-in list. + public CodegenSecurity filterByScopeNames(List filterScopes) { + CodegenSecurity filteredSecurity = new CodegenSecurity(); + // Copy all fields except the scopes. + filteredSecurity.name = name; + filteredSecurity.type = type; + filteredSecurity.hasMore = false; + filteredSecurity.isBasic = isBasic; + filteredSecurity.isBasicBasic = isBasicBasic; + filteredSecurity.isBasicBearer = isBasicBearer; + filteredSecurity.isApiKey = isApiKey; + filteredSecurity.isOAuth = isOAuth; + filteredSecurity.keyParamName = keyParamName; + filteredSecurity.isCode = isCode; + filteredSecurity.isImplicit = isImplicit; + filteredSecurity.isApplication = isApplication; + filteredSecurity.isPassword = isPassword; + filteredSecurity.isKeyInCookie = isKeyInCookie; + filteredSecurity.isKeyInHeader = isKeyInHeader; + filteredSecurity.isKeyInQuery = isKeyInQuery; + filteredSecurity.flow = flow; + filteredSecurity.tokenUrl = tokenUrl; + filteredSecurity.authorizationUrl = authorizationUrl; + // It is not possible to deep copy the extensions, as we have no idea what types they are. + // So the filtered method *will* refer to the original extensions, if any. + filteredSecurity.vendorExtensions = new HashMap(vendorExtensions); + List> returnedScopes = new ArrayList>(); + Map lastScope = null; + for (String filterScopeName : filterScopes) { + for (Map scope : scopes) { + String name = (String) scope.get("scope"); + if (filterScopeName.equals(name)) { + returnedScopes.add(scope); + lastScope = scope; + break; + } + } + } + filteredSecurity.scopes = returnedScopes; + + return filteredSecurity; + } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index 8e1e40d79f3c..86fb5e7f4a45 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -1057,50 +1057,11 @@ private void processOperation(String resourcePath, String httpMethod, Operation } if (authMethods != null && !authMethods.isEmpty()) { - codegenOperation.authMethods = config.fromSecurity(authMethods); - List> scopes = new ArrayList>(); - if (codegenOperation.authMethods != null){ - for (CodegenSecurity security : codegenOperation.authMethods){ - if (security != null && security.isBasicBearer != null && security.isBasicBearer && - securities != null){ - for (SecurityRequirement req : securities){ - if (req == null) continue; - for (String key : req.keySet()){ - if (security.name != null && key.equals(security.name)){ - int count = 0; - for (String sc : req.get(key)){ - Map scope = new HashMap(); - scope.put("scope", sc); - scope.put("description", ""); - count++; - if (req.get(key) != null && count < req.get(key).size()){ - scope.put("hasMore", "true"); - } else { - scope.put("hasMore", null); - } - scopes.add(scope); - } - //end this inner for - break; - } - } - - } - security.hasScopes = scopes.size() > 0; - security.scopes = scopes; - } - } - } + List fullAuthMethods = config.fromSecurity(authMethods); + codegenOperation.authMethods = filterAuthMethods(fullAuthMethods, securities); codegenOperation.hasAuthMethods = true; } - /* TODO need to revise the logic below - Map securitySchemeMap = openAPI.getComponents().getSecuritySchemes(); - if (securitySchemeMap != null && !securitySchemeMap.isEmpty()) { - codegenOperation.authMethods = config.fromSecurity(securitySchemeMap); - codegenOperation.hasAuthMethods = true; - } - */ } catch (Exception ex) { String msg = "Could not process operation:\n" // + " Tag: " + tag + "\n"// @@ -1306,6 +1267,40 @@ private static OAuthFlow cloneOAuthFlow(OAuthFlow originFlow, List opera .scopes(newScopes); } + private List filterAuthMethods(List authMethods, List securities) { + if (securities == null || securities.isEmpty()) { + return authMethods; + } + + List result = new ArrayList(); + + for (CodegenSecurity security : authMethods){ + boolean filtered = false; + if (security != null && security.scopes != null) { + for (SecurityRequirement requirement : securities) { + List opScopes = requirement.get(security.name); + if (opScopes != null) { + // We have operation-level scopes for this method, so filter the auth method to + // describe the operation auth method with only the scopes that it requires. + // We have to create a new auth method instance because the original object must + // not be modified. + CodegenSecurity opSecurity = security.filterByScopeNames(opScopes); + result.add(opSecurity); + filtered = true; + break; + } + } + } + + // If we didn't get a filtered version, then we can keep the original auth method. + if (!filtered) { + result.add(security); + } + } + + return result; + } + private boolean hasOAuthMethods(List authMethods) { for (CodegenSecurity cs : authMethods) { if (Boolean.TRUE.equals(cs.isOAuth)) { diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/controller.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/controller.mustache index 0c136c8a8b8d..7619f90515b6 100644 --- a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/controller.mustache +++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/controller.mustache @@ -33,9 +33,15 @@ namespace {{apiPackage}} /// {{description}}{{/allParams}}{{#responses}} /// {{message}}{{/responses}} [{{httpMethod}}] - [Route("{{{basePathWithoutHost}}}{{{path}}}")]{{#hasAuthMethods}}{{#authMethods}}{{#isApiKey}} - [Authorize(Policy = "{{name}}")]{{/isApiKey}}{{#isBasicBearer}} - [Authorize{{#hasScopes}}(Roles = "{{#scopes}}{{scope}}{{#hasMore}},{{/hasMore}}{{/scopes}}"){{/hasScopes}}]{{/isBasicBearer}}{{/authMethods}}{{/hasAuthMethods}} + [Route("{{{basePathWithoutHost}}}{{{path}}}")] +{{#authMethods}} +{{#isApiKey}} + [Authorize(Policy = "{{name}}")] +{{/isApiKey}} +{{#isBasicBearer}} + [Authorize{{#scopes}}{{#-first}}(Roles = "{{/-first}}{{scope}}{{^-last}},{{/-last}}{{#-last}}"){{/-last}}{{/scopes}}] +{{/isBasicBearer}} +{{/authMethods}} [ValidateModelState]{{#useSwashbuckle}} [SwaggerOperation("{{operationId}}")]{{#responses}}{{#dataType}} [SwaggerResponse(statusCode: {{code}}, type: typeof({{&dataType}}), description: "{{message}}")]{{/dataType}}{{^dataType}}{{/dataType}}{{/responses}}{{/useSwashbuckle}}{{^useSwashbuckle}}{{#responses}}{{#dataType}} diff --git a/modules/openapi-generator/src/test/resources/3_0/rust-server/openapi-v3.yaml b/modules/openapi-generator/src/test/resources/3_0/rust-server/openapi-v3.yaml index 677a439f0412..bbbdd40bf23a 100644 --- a/modules/openapi-generator/src/test/resources/3_0/rust-server/openapi-v3.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/rust-server/openapi-v3.yaml @@ -88,7 +88,32 @@ paths: responses: '200': description: 'OK' + /readonly_auth_scheme: + get: + security: + - authScheme: ["test.read"] + responses: + 200: + description: Check that limiting to a single required auth scheme works + /multiple_auth_scheme: + get: + security: + - authScheme: ["test.read", "test.write"] + responses: + 200: + description: Check that limiting to multiple required auth schemes works + components: + securitySchemes: + authScheme: + type: oauth2 + flows: + authorizationCode: + authorizationUrl: 'http://example.org' + tokenUrl: 'http://example.org' + scopes: + test.read: Allowed to read state. + test.write: Allowed to change state. schemas: UuidObject: description: Test a model containing a UUID diff --git a/samples/server/petstore/aspnetcore/.openapi-generator/VERSION b/samples/server/petstore/aspnetcore/.openapi-generator/VERSION index 06b5019af3f4..83a328a9227e 100644 --- a/samples/server/petstore/aspnetcore/.openapi-generator/VERSION +++ b/samples/server/petstore/aspnetcore/.openapi-generator/VERSION @@ -1 +1 @@ -4.0.1-SNAPSHOT \ No newline at end of file +4.1.0-SNAPSHOT \ No newline at end of file diff --git a/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/PetApi.cs b/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/PetApi.cs index 0df59c9002d6..9e20821e97b2 100644 --- a/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/PetApi.cs +++ b/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/PetApi.cs @@ -16,8 +16,8 @@ using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; using Org.OpenAPITools.Attributes; -using Org.OpenAPITools.Models; using Microsoft.AspNetCore.Authorization; +using Org.OpenAPITools.Models; namespace Org.OpenAPITools.Controllers { diff --git a/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/StoreApi.cs b/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/StoreApi.cs index a94c4a7c243b..c08509d8f03b 100644 --- a/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/StoreApi.cs +++ b/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/StoreApi.cs @@ -16,8 +16,8 @@ using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; using Org.OpenAPITools.Attributes; -using Org.OpenAPITools.Models; using Microsoft.AspNetCore.Authorization; +using Org.OpenAPITools.Models; namespace Org.OpenAPITools.Controllers { diff --git a/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/UserApi.cs b/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/UserApi.cs index c3a29ff7aed9..83792be3f3ad 100644 --- a/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/UserApi.cs +++ b/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Controllers/UserApi.cs @@ -16,8 +16,8 @@ using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; using Org.OpenAPITools.Attributes; -using Org.OpenAPITools.Models; using Microsoft.AspNetCore.Authorization; +using Org.OpenAPITools.Models; namespace Org.OpenAPITools.Controllers { diff --git a/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Models/Order.cs b/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Models/Order.cs index 5312b28df3ce..60620288191a 100644 --- a/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Models/Order.cs +++ b/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/Models/Order.cs @@ -86,7 +86,7 @@ public enum StatusEnum /// Gets or Sets Complete /// [DataMember(Name="complete", EmitDefaultValue=false)] - public bool? Complete { get; set; } + public bool? Complete { get; set; } = false; /// /// Returns the string presentation of the object diff --git a/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/wwwroot/openapi-original.json b/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/wwwroot/openapi-original.json index a0a4803cd077..b39e24282ff4 100644 --- a/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/wwwroot/openapi-original.json +++ b/samples/server/petstore/aspnetcore/src/Org.OpenAPITools/wwwroot/openapi-original.json @@ -112,8 +112,8 @@ "type" : "array", "items" : { "type" : "string", - "default" : "available", - "enum" : [ "available", "pending", "sold" ] + "enum" : [ "available", "pending", "sold" ], + "default" : "available" } } } ], diff --git a/samples/server/petstore/rust-server/output/openapi-v3/README.md b/samples/server/petstore/rust-server/output/openapi-v3/README.md index 706821945d84..a8f397dfc99a 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/README.md +++ b/samples/server/petstore/rust-server/output/openapi-v3/README.md @@ -55,6 +55,8 @@ cargo run --example server To run a client, follow one of the following simple steps: ``` +cargo run --example client MultipleAuthSchemeGet +cargo run --example client ReadonlyAuthSchemeGet cargo run --example client RequiredOctetStreamPut cargo run --example client XmlExtraPost cargo run --example client XmlOtherPost @@ -114,6 +116,8 @@ All URIs are relative to *http://localhost* Method | HTTP request | Description ------------- | ------------- | ------------- +[****](docs/default_api.md#) | **GET** /multiple_auth_scheme | +[****](docs/default_api.md#) | **GET** /readonly_auth_scheme | [****](docs/default_api.md#) | **PUT** /required_octet_stream | [****](docs/default_api.md#) | **POST** /xml_extra | [****](docs/default_api.md#) | **POST** /xml_other | @@ -135,8 +139,22 @@ Method | HTTP request | Description ## Documentation For Authorization - Endpoints do not require authorization. +## authScheme +- **Type**: OAuth +- **Flow**: accessCode +- **Authorization URL**: http://example.org +- **Scopes**: + - **test.read**: Allowed to read state. + - **test.write**: Allowed to change state. + +Example +``` +``` + +Or via OAuth2 module to automatically refresh tokens and perform user authentication. +``` +``` ## Author diff --git a/samples/server/petstore/rust-server/output/openapi-v3/api/openapi.yaml b/samples/server/petstore/rust-server/output/openapi-v3/api/openapi.yaml index f9282ee31152..15b7e417e166 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/api/openapi.yaml +++ b/samples/server/petstore/rust-server/output/openapi-v3/api/openapi.yaml @@ -77,6 +77,23 @@ paths: responses: 200: description: OK + /readonly_auth_scheme: + get: + responses: + 200: + description: Check that limiting to a single required auth scheme works + security: + - authScheme: + - test.read + /multiple_auth_scheme: + get: + responses: + 200: + description: Check that limiting to multiple required auth schemes works + security: + - authScheme: + - test.read + - test.write components: schemas: UuidObject: @@ -139,4 +156,14 @@ components: xml: name: snake_another_xml_object namespace: http://foo.bar + securitySchemes: + authScheme: + flows: + authorizationCode: + authorizationUrl: http://example.org + scopes: + test.read: Allowed to read state. + test.write: Allowed to change state. + tokenUrl: http://example.org + type: oauth2 diff --git a/samples/server/petstore/rust-server/output/openapi-v3/docs/default_api.md b/samples/server/petstore/rust-server/output/openapi-v3/docs/default_api.md index b8652374c9d0..863a2bc464d7 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/docs/default_api.md +++ b/samples/server/petstore/rust-server/output/openapi-v3/docs/default_api.md @@ -4,6 +4,8 @@ All URIs are relative to *http://localhost* Method | HTTP request | Description ------------- | ------------- | ------------- +****](default_api.md#) | **GET** /multiple_auth_scheme | +****](default_api.md#) | **GET** /readonly_auth_scheme | ****](default_api.md#) | **PUT** /required_octet_stream | ****](default_api.md#) | **POST** /xml_extra | ****](default_api.md#) | **POST** /xml_other | @@ -12,6 +14,50 @@ Method | HTTP request | Description ****](default_api.md#) | **PUT** /xml | +# **** +> (ctx, ) + + +### Required Parameters +This endpoint does not need any parameter. + +### Return type + + (empty response body) + +### Authorization + +[authScheme](../README.md#authScheme) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **** +> (ctx, ) + + +### Required Parameters +This endpoint does not need any parameter. + +### Return type + + (empty response body) + +### Authorization + +[authScheme](../README.md#authScheme) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **** > (body) diff --git a/samples/server/petstore/rust-server/output/openapi-v3/examples/client.rs b/samples/server/petstore/rust-server/output/openapi-v3/examples/client.rs index b94f73479676..fb333db35972 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/examples/client.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/examples/client.rs @@ -19,6 +19,8 @@ use tokio_core::reactor; #[allow(unused_imports)] use openapi_v3::{ApiNoContext, ContextWrapperExt, ApiError, + MultipleAuthSchemeGetResponse, + ReadonlyAuthSchemeGetResponse, RequiredOctetStreamPutResponse, XmlExtraPostResponse, XmlOtherPostResponse, @@ -33,6 +35,8 @@ fn main() { .arg(Arg::with_name("operation") .help("Sets the operation to run") .possible_values(&[ + "MultipleAuthSchemeGet", + "ReadonlyAuthSchemeGet", "RequiredOctetStreamPut", "XmlExtraPost", "XmlOtherPost", @@ -79,6 +83,16 @@ fn main() { match matches.value_of("operation") { + Some("MultipleAuthSchemeGet") => { + let result = core.run(client.multiple_auth_scheme_get()); + println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has).get().clone()); + }, + + Some("ReadonlyAuthSchemeGet") => { + let result = core.run(client.readonly_auth_scheme_get()); + println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has).get().clone()); + }, + Some("RequiredOctetStreamPut") => { let result = core.run(client.required_octet_stream_put(swagger::ByteArray(Vec::from("BYTE_ARRAY_DATA_HERE")))); println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has).get().clone()); diff --git a/samples/server/petstore/rust-server/output/openapi-v3/examples/server_lib/mod.rs b/samples/server/petstore/rust-server/output/openapi-v3/examples/server_lib/mod.rs index ad8904ca16bd..2139af63155f 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/examples/server_lib/mod.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/examples/server_lib/mod.rs @@ -13,6 +13,7 @@ use std::marker::PhantomData; use hyper; use openapi_v3; use swagger::{Has, XSpanIdString}; +use swagger::auth::Authorization; pub struct NewService{ marker: PhantomData @@ -24,7 +25,7 @@ impl NewService{ } } -impl hyper::server::NewService for NewService where C: Has + Clone + 'static { +impl hyper::server::NewService for NewService where C: Has + Has> + Clone + 'static { type Request = (hyper::Request, C); type Response = hyper::Response; type Error = hyper::Error; diff --git a/samples/server/petstore/rust-server/output/openapi-v3/examples/server_lib/server.rs b/samples/server/petstore/rust-server/output/openapi-v3/examples/server_lib/server.rs index 9e28fb179abf..e2f70f289f17 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/examples/server_lib/server.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/examples/server_lib/server.rs @@ -11,6 +11,8 @@ use swagger; use swagger::{Has, XSpanIdString}; use openapi_v3::{Api, ApiError, + MultipleAuthSchemeGetResponse, + ReadonlyAuthSchemeGetResponse, RequiredOctetStreamPutResponse, XmlExtraPostResponse, XmlOtherPostResponse, @@ -34,6 +36,20 @@ impl Server { impl Api for Server where C: Has{ + fn multiple_auth_scheme_get(&self, context: &C) -> Box> { + let context = context.clone(); + println!("multiple_auth_scheme_get() - X-Span-ID: {:?}", context.get().0.clone()); + Box::new(futures::failed("Generic failure".into())) + } + + + fn readonly_auth_scheme_get(&self, context: &C) -> Box> { + let context = context.clone(); + println!("readonly_auth_scheme_get() - X-Span-ID: {:?}", context.get().0.clone()); + Box::new(futures::failed("Generic failure".into())) + } + + fn required_octet_stream_put(&self, body: swagger::ByteArray, context: &C) -> Box> { let context = context.clone(); println!("required_octet_stream_put({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); diff --git a/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs b/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs index 855f89cfce35..ff61e002fd27 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/src/client/mod.rs @@ -40,6 +40,8 @@ use swagger; use swagger::{ApiError, XSpanId, XSpanIdString, Has, AuthData}; use {Api, + MultipleAuthSchemeGetResponse, + ReadonlyAuthSchemeGetResponse, RequiredOctetStreamPutResponse, XmlExtraPostResponse, XmlOtherPostResponse, @@ -249,7 +251,133 @@ impl Client where impl Api for Client where F: Future + 'static, - C: Has { + C: Has + Has>{ + + fn multiple_auth_scheme_get(&self, context: &C) -> Box> { + let mut uri = format!( + "{}/multiple_auth_scheme", + self.base_path + ); + + let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned()); + + + let query_string_str = query_string.finish(); + if !query_string_str.is_empty() { + uri += "?"; + uri += &query_string_str; + } + + let uri = match Uri::from_str(&uri) { + Ok(uri) => uri, + Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to build URI: {}", err))))), + }; + + let mut request = hyper::Request::new(hyper::Method::Get, uri); + + + request.headers_mut().set(XSpanId((context as &Has).get().0.clone())); + + + Box::new(self.client_service.call(request) + .map_err(|e| ApiError(format!("No response received: {}", e))) + .and_then(|mut response| { + match response.status().as_u16() { + 200 => { + let body = response.body(); + Box::new( + + future::ok( + MultipleAuthSchemeGetResponse::CheckThatLimitingToMultipleRequiredAuthSchemesWorks + ) + ) as Box> + }, + code => { + let headers = response.headers().clone(); + Box::new(response.body() + .take(100) + .concat2() + .then(move |body| + future::err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}", + code, + headers, + match body { + Ok(ref body) => match str::from_utf8(body) { + Ok(body) => Cow::from(body), + Err(e) => Cow::from(format!("", e)), + }, + Err(e) => Cow::from(format!("", e)), + }))) + ) + ) as Box> + } + } + })) + + } + + fn readonly_auth_scheme_get(&self, context: &C) -> Box> { + let mut uri = format!( + "{}/readonly_auth_scheme", + self.base_path + ); + + let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned()); + + + let query_string_str = query_string.finish(); + if !query_string_str.is_empty() { + uri += "?"; + uri += &query_string_str; + } + + let uri = match Uri::from_str(&uri) { + Ok(uri) => uri, + Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to build URI: {}", err))))), + }; + + let mut request = hyper::Request::new(hyper::Method::Get, uri); + + + request.headers_mut().set(XSpanId((context as &Has).get().0.clone())); + + + Box::new(self.client_service.call(request) + .map_err(|e| ApiError(format!("No response received: {}", e))) + .and_then(|mut response| { + match response.status().as_u16() { + 200 => { + let body = response.body(); + Box::new( + + future::ok( + ReadonlyAuthSchemeGetResponse::CheckThatLimitingToASingleRequiredAuthSchemeWorks + ) + ) as Box> + }, + code => { + let headers = response.headers().clone(); + Box::new(response.body() + .take(100) + .concat2() + .then(move |body| + future::err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}", + code, + headers, + match body { + Ok(ref body) => match str::from_utf8(body) { + Ok(body) => Cow::from(body), + Err(e) => Cow::from(format!("", e)), + }, + Err(e) => Cow::from(format!("", e)), + }))) + ) + ) as Box> + } + } + })) + + } fn required_octet_stream_put(&self, param_body: swagger::ByteArray, context: &C) -> Box> { let mut uri = format!( @@ -273,8 +401,6 @@ impl Api for Client where let mut request = hyper::Request::new(hyper::Method::Put, uri); - - // Body parameter let body = param_body.0; request.set_body(body); diff --git a/samples/server/petstore/rust-server/output/openapi-v3/src/lib.rs b/samples/server/petstore/rust-server/output/openapi-v3/src/lib.rs index 45731e516ace..e0b42aadcee0 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/src/lib.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/src/lib.rs @@ -39,6 +39,18 @@ pub const BASE_PATH: &'static str = ""; pub const API_VERSION: &'static str = "1.0.7"; +#[derive(Debug, PartialEq)] +pub enum MultipleAuthSchemeGetResponse { + /// Check that limiting to multiple required auth schemes works + CheckThatLimitingToMultipleRequiredAuthSchemesWorks , +} + +#[derive(Debug, PartialEq)] +pub enum ReadonlyAuthSchemeGetResponse { + /// Check that limiting to a single required auth scheme works + CheckThatLimitingToASingleRequiredAuthSchemeWorks , +} + #[derive(Debug, PartialEq)] pub enum RequiredOctetStreamPutResponse { /// OK @@ -90,6 +102,12 @@ pub enum XmlPutResponse { pub trait Api { + fn multiple_auth_scheme_get(&self, context: &C) -> Box>; + + + fn readonly_auth_scheme_get(&self, context: &C) -> Box>; + + fn required_octet_stream_put(&self, body: swagger::ByteArray, context: &C) -> Box>; @@ -113,6 +131,12 @@ pub trait Api { pub trait ApiNoContext { + fn multiple_auth_scheme_get(&self) -> Box>; + + + fn readonly_auth_scheme_get(&self) -> Box>; + + fn required_octet_stream_put(&self, body: swagger::ByteArray) -> Box>; @@ -147,6 +171,16 @@ impl<'a, T: Api + Sized, C> ContextWrapperExt<'a, C> for T { impl<'a, T: Api, C> ApiNoContext for ContextWrapper<'a, T, C> { + fn multiple_auth_scheme_get(&self) -> Box> { + self.api().multiple_auth_scheme_get(&self.context()) + } + + + fn readonly_auth_scheme_get(&self) -> Box> { + self.api().readonly_auth_scheme_get(&self.context()) + } + + fn required_octet_stream_put(&self, body: swagger::ByteArray) -> Box> { self.api().required_octet_stream_put(body, &self.context()) } diff --git a/samples/server/petstore/rust-server/output/openapi-v3/src/server/context.rs b/samples/server/petstore/rust-server/output/openapi-v3/src/server/context.rs index 6f2900b3d70c..a377b535b623 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/src/server/context.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/src/server/context.rs @@ -83,6 +83,16 @@ impl hyper::server::Service for AddContext fn call(&self, req: Self::Request) -> Self::Future { let context = A::default().push(XSpanIdString::get_or_generate(&req)); + { + use hyper::header::{Authorization as HyperAuth, Basic, Bearer}; + use std::ops::Deref; + if let Some(bearer) = req.headers().get::>().cloned() { + let auth_data = AuthData::Bearer(bearer.deref().clone()); + let context = context.push(Some(auth_data)); + let context = context.push(None::); + return self.inner.call((req, context)); + } + } let context = context.push(None::); let context = context.push(None::); diff --git a/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs b/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs index f96ba0a9c5dc..002fb1561ca2 100644 --- a/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs +++ b/samples/server/petstore/rust-server/output/openapi-v3/src/server/mod.rs @@ -37,6 +37,8 @@ use swagger::{ApiError, XSpanId, XSpanIdString, Has, RequestParser}; use swagger::auth::Scopes; use {Api, + MultipleAuthSchemeGetResponse, + ReadonlyAuthSchemeGetResponse, RequiredOctetStreamPutResponse, XmlExtraPostResponse, XmlOtherPostResponse, @@ -56,16 +58,20 @@ mod paths { lazy_static! { pub static ref GLOBAL_REGEX_SET: regex::RegexSet = regex::RegexSet::new(&[ + r"^/multiple_auth_scheme$", + r"^/readonly_auth_scheme$", r"^/required_octet_stream$", r"^/xml$", r"^/xml_extra$", r"^/xml_other$" ]).unwrap(); } - pub static ID_REQUIRED_OCTET_STREAM: usize = 0; - pub static ID_XML: usize = 1; - pub static ID_XML_EXTRA: usize = 2; - pub static ID_XML_OTHER: usize = 3; + pub static ID_MULTIPLE_AUTH_SCHEME: usize = 0; + pub static ID_READONLY_AUTH_SCHEME: usize = 1; + pub static ID_REQUIRED_OCTET_STREAM: usize = 2; + pub static ID_XML: usize = 3; + pub static ID_XML_EXTRA: usize = 4; + pub static ID_XML_OTHER: usize = 5; } pub struct NewService { @@ -76,7 +82,7 @@ pub struct NewService { impl NewService where T: Api + Clone + 'static, - C: Has + 'static + C: Has + Has> + 'static { pub fn new>>(api_impl: U) -> NewService { NewService{api_impl: api_impl.into(), marker: PhantomData} @@ -86,7 +92,7 @@ where impl hyper::server::NewService for NewService where T: Api + Clone + 'static, - C: Has + 'static + C: Has + Has> + 'static { type Request = (Request, C); type Response = Response; @@ -106,7 +112,7 @@ pub struct Service { impl Service where T: Api + Clone + 'static, - C: Has + 'static { + C: Has + Has> + 'static { pub fn new>>(api_impl: U) -> Service { Service{api_impl: api_impl.into(), marker: PhantomData} } @@ -115,7 +121,7 @@ where impl hyper::server::Service for Service where T: Api + Clone + 'static, - C: Has + 'static + C: Has + Has> + 'static { type Request = (Request, C); type Response = Response; @@ -131,6 +137,151 @@ where // Please update both places if changing how this code is autogenerated. match &method { + // MultipleAuthSchemeGet - GET /multiple_auth_scheme + &hyper::Method::Get if path.matched(paths::ID_MULTIPLE_AUTH_SCHEME) => { + { + let authorization = match (&context as &Has>).get() { + &Some(ref authorization) => authorization, + &None => return Box::new(future::ok(Response::new() + .with_status(StatusCode::Forbidden) + .with_body("Unauthenticated"))), + }; + + // Authorization + if let Scopes::Some(ref scopes) = authorization.scopes { + let required_scopes: BTreeSet = vec![ + "test.read".to_string(), // Allowed to read state. + "test.write".to_string(), // Allowed to change state. + ].into_iter().collect(); + + if !required_scopes.is_subset(scopes) { + let missing_scopes = required_scopes.difference(scopes); + return Box::new(future::ok(Response::new() + .with_status(StatusCode::Forbidden) + .with_body(missing_scopes.fold( + "Insufficient authorization, missing scopes".to_string(), + |s, scope| format!("{} {}", s, scope) + )) + )); + } + } + } + + + + + + + + Box::new({ + {{ + + Box::new(api_impl.multiple_auth_scheme_get(&context) + .then(move |result| { + let mut response = Response::new(); + response.headers_mut().set(XSpanId((&context as &Has).get().0.to_string())); + + match result { + Ok(rsp) => match rsp { + MultipleAuthSchemeGetResponse::CheckThatLimitingToMultipleRequiredAuthSchemesWorks + + + => { + response.set_status(StatusCode::try_from(200).unwrap()); + + }, + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + response.set_status(StatusCode::InternalServerError); + response.set_body("An internal error occurred"); + }, + } + + future::ok(response) + } + )) + + }} + }) as Box> + + + }, + + + // ReadonlyAuthSchemeGet - GET /readonly_auth_scheme + &hyper::Method::Get if path.matched(paths::ID_READONLY_AUTH_SCHEME) => { + { + let authorization = match (&context as &Has>).get() { + &Some(ref authorization) => authorization, + &None => return Box::new(future::ok(Response::new() + .with_status(StatusCode::Forbidden) + .with_body("Unauthenticated"))), + }; + + // Authorization + if let Scopes::Some(ref scopes) = authorization.scopes { + let required_scopes: BTreeSet = vec![ + "test.read".to_string(), // Allowed to read state. + ].into_iter().collect(); + + if !required_scopes.is_subset(scopes) { + let missing_scopes = required_scopes.difference(scopes); + return Box::new(future::ok(Response::new() + .with_status(StatusCode::Forbidden) + .with_body(missing_scopes.fold( + "Insufficient authorization, missing scopes".to_string(), + |s, scope| format!("{} {}", s, scope) + )) + )); + } + } + } + + + + + + + + Box::new({ + {{ + + Box::new(api_impl.readonly_auth_scheme_get(&context) + .then(move |result| { + let mut response = Response::new(); + response.headers_mut().set(XSpanId((&context as &Has).get().0.to_string())); + + match result { + Ok(rsp) => match rsp { + ReadonlyAuthSchemeGetResponse::CheckThatLimitingToASingleRequiredAuthSchemeWorks + + + => { + response.set_status(StatusCode::try_from(200).unwrap()); + + }, + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + response.set_status(StatusCode::InternalServerError); + response.set_body("An internal error occurred"); + }, + } + + future::ok(response) + } + )) + + }} + }) as Box> + + + }, + + // RequiredOctetStreamPut - PUT /required_octet_stream &hyper::Method::Put if path.matched(paths::ID_REQUIRED_OCTET_STREAM) => { @@ -628,6 +779,12 @@ impl RequestParser for ApiRequestParser { let path = paths::GLOBAL_REGEX_SET.matches(request.uri().path()); match request.method() { + // MultipleAuthSchemeGet - GET /multiple_auth_scheme + &hyper::Method::Get if path.matched(paths::ID_MULTIPLE_AUTH_SCHEME) => Ok("MultipleAuthSchemeGet"), + + // ReadonlyAuthSchemeGet - GET /readonly_auth_scheme + &hyper::Method::Get if path.matched(paths::ID_READONLY_AUTH_SCHEME) => Ok("ReadonlyAuthSchemeGet"), + // RequiredOctetStreamPut - PUT /required_octet_stream &hyper::Method::Put if path.matched(paths::ID_REQUIRED_OCTET_STREAM) => Ok("RequiredOctetStreamPut"), From 7fa164b20878b907e29f7623b5ca929f70d02d29 Mon Sep 17 00:00:00 2001 From: Richard Whitehouse Date: Sat, 21 Sep 2019 11:41:04 +0100 Subject: [PATCH 2/3] [Core] Fix NPE in filtering auth methods --- .../main/java/org/openapitools/codegen/DefaultGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index 86fb5e7f4a45..82cc200b5f39 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -1268,7 +1268,7 @@ private static OAuthFlow cloneOAuthFlow(OAuthFlow originFlow, List opera } private List filterAuthMethods(List authMethods, List securities) { - if (securities == null || securities.isEmpty()) { + if (securities == null || securities.isEmpty() || authMethods == null) { return authMethods; } From 664c71c4f2071ad68c8686739992f6e6caf2df2e Mon Sep 17 00:00:00 2001 From: Richard Whitehouse Date: Sun, 22 Sep 2019 11:13:49 +0100 Subject: [PATCH 3/3] [Core] Fix filtering global authentication schemes --- .../org/openapitools/codegen/DefaultGenerator.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index 01e66d0cfd03..b4e8da482033 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -1056,14 +1056,19 @@ private void processOperation(String resourcePath, String httpMethod, Operation } Map authMethods = getAuthMethods(securities, securitySchemes); - if (authMethods == null || authMethods.isEmpty()) { - authMethods = getAuthMethods(globalSecurities, securitySchemes); - } if (authMethods != null && !authMethods.isEmpty()) { List fullAuthMethods = config.fromSecurity(authMethods); codegenOperation.authMethods = filterAuthMethods(fullAuthMethods, securities); codegenOperation.hasAuthMethods = true; + } else { + authMethods = getAuthMethods(globalSecurities, securitySchemes); + + if (authMethods != null && !authMethods.isEmpty()) { + List fullAuthMethods = config.fromSecurity(authMethods); + codegenOperation.authMethods = filterAuthMethods(fullAuthMethods, globalSecurities); + codegenOperation.hasAuthMethods = true; + } } } catch (Exception ex) {