[Core, Rust Server, ASP.NET Core] Fix Codegen Operation Scope Consistency (#3495)

Fix Codegen Operation Scope Consistency

- Filter scopes based on operation

- Partially revert #1984 to not rely on custom attributes as to whether scopes exist

- Fix filtering global authentication schemes
This commit is contained in:
Richard Whitehouse 2019-11-08 13:30:35 +00:00 committed by GitHub
parent 5b4441892f
commit de162f7f34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1022 additions and 501 deletions

View File

@ -17,6 +17,7 @@
package org.openapitools.codegen; package org.openapitools.codegen;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -38,7 +39,7 @@ public class CodegenSecurity {
// Oauth specific // Oauth specific
public String flow, authorizationUrl, tokenUrl; public String flow, authorizationUrl, tokenUrl;
public List<Map<String, Object>> scopes; public List<Map<String, Object>> scopes;
public Boolean isCode, isPassword, isApplication, isImplicit, hasScopes; public Boolean isCode, isPassword, isApplication, isImplicit;
@Override @Override
public String toString() { public String toString() {
@ -100,4 +101,47 @@ public class CodegenSecurity {
isImplicit, isImplicit,
scopes); scopes);
} }
// Return a copy of the security object, filtering out any scopes from the passed-in list.
public CodegenSecurity filterByScopeNames(List<String> 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<String, Object>(vendorExtensions);
List<Map<String, Object>> returnedScopes = new ArrayList<Map<String, Object>>();
Map<String, Object> lastScope = null;
for (String filterScopeName : filterScopes) {
for (Map<String, Object> scope : scopes) {
String name = (String) scope.get("scope");
if (filterScopeName.equals(name)) {
returnedScopes.add(scope);
lastScope = scope;
break;
}
}
}
filteredSecurity.scopes = returnedScopes;
return filteredSecurity;
}
} }

View File

@ -1057,55 +1057,21 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
} }
Map<String, SecurityScheme> authMethods = getAuthMethods(securities, securitySchemes); Map<String, SecurityScheme> authMethods = getAuthMethods(securities, securitySchemes);
if (authMethods == null || authMethods.isEmpty()) {
authMethods = getAuthMethods(globalSecurities, securitySchemes);
}
if (authMethods != null && !authMethods.isEmpty()) { if (authMethods != null && !authMethods.isEmpty()) {
codegenOperation.authMethods = config.fromSecurity(authMethods); List<CodegenSecurity> fullAuthMethods = config.fromSecurity(authMethods);
List<Map<String, Object>> scopes = new ArrayList<Map<String, Object>>(); codegenOperation.authMethods = filterAuthMethods(fullAuthMethods, securities);
if (codegenOperation.authMethods != null) { codegenOperation.hasAuthMethods = true;
for (CodegenSecurity security : codegenOperation.authMethods) { } else {
if (security != null && security.isBasicBearer != null && security.isBasicBearer && authMethods = getAuthMethods(globalSecurities, securitySchemes);
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<String, Object> scope = new HashMap<String, Object>();
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;
}
}
} if (authMethods != null && !authMethods.isEmpty()) {
security.hasScopes = scopes.size() > 0; List<CodegenSecurity> fullAuthMethods = config.fromSecurity(authMethods);
security.scopes = scopes; codegenOperation.authMethods = filterAuthMethods(fullAuthMethods, globalSecurities);
} codegenOperation.hasAuthMethods = true;
}
} }
codegenOperation.hasAuthMethods = true;
} }
/* TODO need to revise the logic below
Map<String, SecurityScheme> securitySchemeMap = openAPI.getComponents().getSecuritySchemes();
if (securitySchemeMap != null && !securitySchemeMap.isEmpty()) {
codegenOperation.authMethods = config.fromSecurity(securitySchemeMap);
codegenOperation.hasAuthMethods = true;
}
*/
} catch (Exception ex) { } catch (Exception ex) {
String msg = "Could not process operation:\n" // String msg = "Could not process operation:\n" //
+ " Tag: " + tag + "\n"// + " Tag: " + tag + "\n"//
@ -1311,6 +1277,40 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
.scopes(newScopes); .scopes(newScopes);
} }
private List<CodegenSecurity> filterAuthMethods(List<CodegenSecurity> authMethods, List<SecurityRequirement> securities) {
if (securities == null || securities.isEmpty() || authMethods == null) {
return authMethods;
}
List<CodegenSecurity> result = new ArrayList<CodegenSecurity>();
for (CodegenSecurity security : authMethods) {
boolean filtered = false;
if (security != null && security.scopes != null) {
for (SecurityRequirement requirement : securities) {
List<String> 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<CodegenSecurity> authMethods) { private boolean hasOAuthMethods(List<CodegenSecurity> authMethods) {
for (CodegenSecurity cs : authMethods) { for (CodegenSecurity cs : authMethods) {
if (Boolean.TRUE.equals(cs.isOAuth)) { if (Boolean.TRUE.equals(cs.isOAuth)) {

View File

@ -33,9 +33,15 @@ namespace {{apiPackage}}
/// <param name="{{paramName}}">{{description}}</param>{{/allParams}}{{#responses}} /// <param name="{{paramName}}">{{description}}</param>{{/allParams}}{{#responses}}
/// <response code="{{code}}">{{message}}</response>{{/responses}} /// <response code="{{code}}">{{message}}</response>{{/responses}}
[{{httpMethod}}] [{{httpMethod}}]
[Route("{{{basePathWithoutHost}}}{{{path}}}")]{{#hasAuthMethods}}{{#authMethods}}{{#isApiKey}} [Route("{{{basePathWithoutHost}}}{{{path}}}")]
[Authorize(Policy = "{{name}}")]{{/isApiKey}}{{#isBasicBearer}} {{#authMethods}}
[Authorize{{#hasScopes}}(Roles = "{{#scopes}}{{scope}}{{#hasMore}},{{/hasMore}}{{/scopes}}"){{/hasScopes}}]{{/isBasicBearer}}{{/authMethods}}{{/hasAuthMethods}} {{#isApiKey}}
[Authorize(Policy = "{{name}}")]
{{/isApiKey}}
{{#isBasicBearer}}
[Authorize{{#scopes}}{{#-first}}(Roles = "{{/-first}}{{scope}}{{^-last}},{{/-last}}{{#-last}}"){{/-last}}{{/scopes}}]
{{/isBasicBearer}}
{{/authMethods}}
[ValidateModelState]{{#useSwashbuckle}} [ValidateModelState]{{#useSwashbuckle}}
[SwaggerOperation("{{operationId}}")]{{#responses}}{{#dataType}} [SwaggerOperation("{{operationId}}")]{{#responses}}{{#dataType}}
[SwaggerResponse(statusCode: {{code}}, type: typeof({{&dataType}}), description: "{{message}}")]{{/dataType}}{{^dataType}}{{/dataType}}{{/responses}}{{/useSwashbuckle}}{{^useSwashbuckle}}{{#responses}}{{#dataType}} [SwaggerResponse(statusCode: {{code}}, type: typeof({{&dataType}}), description: "{{message}}")]{{/dataType}}{{^dataType}}{{/dataType}}{{/responses}}{{/useSwashbuckle}}{{^useSwashbuckle}}{{#responses}}{{#dataType}}

View File

@ -101,6 +101,20 @@ paths:
responses: responses:
'200': '200':
description: 'OK' 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
/responses_with_headers: /responses_with_headers:
get: get:
responses: responses:
@ -123,7 +137,18 @@ paths:
Failure-Info: Failure-Info:
schema: schema:
type: String type: String
components: 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: schemas:
UuidObject: UuidObject:
description: Test a model containing a UUID description: Test a model containing a UUID

View File

@ -1 +1 @@
4.1.1-SNAPSHOT 4.1.3-SNAPSHOT

View File

@ -61,6 +61,8 @@ cargo run --example server
To run a client, follow one of the following simple steps: 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 RequiredOctetStreamPut
cargo run --example client ResponsesWithHeadersGet cargo run --example client ResponsesWithHeadersGet
cargo run --example client UuidGet cargo run --example client UuidGet
@ -102,6 +104,8 @@ All URIs are relative to *http://localhost*
Method | HTTP request | Description 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#) | **PUT** /required_octet_stream |
[****](docs/default_api.md#) | **GET** /responses_with_headers | [****](docs/default_api.md#) | **GET** /responses_with_headers |
[****](docs/default_api.md#) | **GET** /uuid | [****](docs/default_api.md#) | **GET** /uuid |
@ -125,8 +129,22 @@ Method | HTTP request | Description
## Documentation For Authorization ## 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 ## Author

View File

@ -86,6 +86,23 @@ paths:
responses: responses:
200: 200:
description: OK 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
/responses_with_headers: /responses_with_headers:
get: get:
responses: responses:
@ -176,4 +193,14 @@ components:
xml: xml:
name: snake_another_xml_object name: snake_another_xml_object
namespace: http://foo.bar 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

View File

@ -4,6 +4,8 @@ All URIs are relative to *http://localhost*
Method | HTTP request | Description 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#) | **PUT** /required_octet_stream |
****](default_api.md#) | **GET** /responses_with_headers | ****](default_api.md#) | **GET** /responses_with_headers |
****](default_api.md#) | **GET** /uuid | ****](default_api.md#) | **GET** /uuid |
@ -14,6 +16,50 @@ Method | HTTP request | Description
****](default_api.md#) | **PUT** /xml | ****](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) > (body)

View File

@ -19,6 +19,8 @@ use tokio_core::reactor;
#[allow(unused_imports)] #[allow(unused_imports)]
use openapi_v3::{ApiNoContext, ContextWrapperExt, use openapi_v3::{ApiNoContext, ContextWrapperExt,
ApiError, ApiError,
MultipleAuthSchemeGetResponse,
ReadonlyAuthSchemeGetResponse,
RequiredOctetStreamPutResponse, RequiredOctetStreamPutResponse,
ResponsesWithHeadersGetResponse, ResponsesWithHeadersGetResponse,
UuidGetResponse, UuidGetResponse,
@ -35,6 +37,8 @@ fn main() {
.arg(Arg::with_name("operation") .arg(Arg::with_name("operation")
.help("Sets the operation to run") .help("Sets the operation to run")
.possible_values(&[ .possible_values(&[
"MultipleAuthSchemeGet",
"ReadonlyAuthSchemeGet",
"RequiredOctetStreamPut", "RequiredOctetStreamPut",
"ResponsesWithHeadersGet", "ResponsesWithHeadersGet",
"UuidGet", "UuidGet",
@ -83,6 +87,16 @@ fn main() {
match matches.value_of("operation") { 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<XSpanIdString>).get().clone());
},
Some("ReadonlyAuthSchemeGet") => {
let result = core.run(client.readonly_auth_scheme_get());
println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has<XSpanIdString>).get().clone());
},
Some("RequiredOctetStreamPut") => { Some("RequiredOctetStreamPut") => {
let result = core.run(client.required_octet_stream_put(swagger::ByteArray(Vec::from("BYTE_ARRAY_DATA_HERE")))); 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<XSpanIdString>).get().clone()); println!("{:?} (X-Span-ID: {:?})", result, (client.context() as &Has<XSpanIdString>).get().clone());

View File

@ -13,6 +13,7 @@ use std::marker::PhantomData;
use hyper; use hyper;
use openapi_v3; use openapi_v3;
use swagger::{Has, XSpanIdString}; use swagger::{Has, XSpanIdString};
use swagger::auth::Authorization;
pub struct NewService<C>{ pub struct NewService<C>{
marker: PhantomData<C> marker: PhantomData<C>
@ -24,7 +25,7 @@ impl<C> NewService<C>{
} }
} }
impl<C> hyper::server::NewService for NewService<C> where C: Has<XSpanIdString> + Clone + 'static { impl<C> hyper::server::NewService for NewService<C> where C: Has<XSpanIdString> + Has<Option<Authorization>> + Clone + 'static {
type Request = (hyper::Request, C); type Request = (hyper::Request, C);
type Response = hyper::Response; type Response = hyper::Response;
type Error = hyper::Error; type Error = hyper::Error;

View File

@ -11,6 +11,8 @@ use swagger::{Has, XSpanIdString};
use uuid; use uuid;
use openapi_v3::{Api, ApiError, use openapi_v3::{Api, ApiError,
MultipleAuthSchemeGetResponse,
ReadonlyAuthSchemeGetResponse,
RequiredOctetStreamPutResponse, RequiredOctetStreamPutResponse,
ResponsesWithHeadersGetResponse, ResponsesWithHeadersGetResponse,
UuidGetResponse, UuidGetResponse,
@ -36,6 +38,20 @@ impl<C> Server<C> {
impl<C> Api<C> for Server<C> where C: Has<XSpanIdString>{ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString>{
fn multiple_auth_scheme_get(&self, context: &C) -> Box<Future<Item=MultipleAuthSchemeGetResponse, Error=ApiError>> {
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<Future<Item=ReadonlyAuthSchemeGetResponse, Error=ApiError>> {
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<Future<Item=RequiredOctetStreamPutResponse, Error=ApiError>> { fn required_octet_stream_put(&self, body: swagger::ByteArray, context: &C) -> Box<Future<Item=RequiredOctetStreamPutResponse, Error=ApiError>> {
let context = context.clone(); let context = context.clone();
println!("required_octet_stream_put({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); println!("required_octet_stream_put({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());

View File

@ -37,6 +37,8 @@ use swagger;
use swagger::{ApiError, XSpanId, XSpanIdString, Has, AuthData}; use swagger::{ApiError, XSpanId, XSpanIdString, Has, AuthData};
use {Api, use {Api,
MultipleAuthSchemeGetResponse,
ReadonlyAuthSchemeGetResponse,
RequiredOctetStreamPutResponse, RequiredOctetStreamPutResponse,
ResponsesWithHeadersGetResponse, ResponsesWithHeadersGetResponse,
UuidGetResponse, UuidGetResponse,
@ -248,7 +250,153 @@ impl<F> Client<F> where
impl<F, C> Api<C> for Client<F> where impl<F, C> Api<C> for Client<F> where
F: Future<Item=hyper::Response, Error=hyper::Error> + 'static, F: Future<Item=hyper::Response, Error=hyper::Error> + 'static,
C: Has<XSpanIdString> { C: Has<XSpanIdString> + Has<Option<AuthData>>{
fn multiple_auth_scheme_get(&self, context: &C) -> Box<Future<Item=MultipleAuthSchemeGetResponse, Error=ApiError>> {
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<XSpanIdString>).get().0.clone()));
(context as &Has<Option<AuthData>>).get().as_ref().map(|auth_data| {
// Currently only authentication with Basic, API Key, and Bearer are supported
match auth_data {
&AuthData::Bearer(ref bearer_header) => {
request.headers_mut().set(hyper::header::Authorization(
bearer_header.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<Future<Item=_, Error=_>>
},
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!("<Body was not UTF8: {:?}>", e)),
},
Err(e) => Cow::from(format!("<Failed to read body: {}>", e)),
})))
)
) as Box<Future<Item=_, Error=_>>
}
}
}))
}
fn readonly_auth_scheme_get(&self, context: &C) -> Box<Future<Item=ReadonlyAuthSchemeGetResponse, Error=ApiError>> {
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<XSpanIdString>).get().0.clone()));
(context as &Has<Option<AuthData>>).get().as_ref().map(|auth_data| {
// Currently only authentication with Basic, API Key, and Bearer are supported
match auth_data {
&AuthData::Bearer(ref bearer_header) => {
request.headers_mut().set(hyper::header::Authorization(
bearer_header.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<Future<Item=_, Error=_>>
},
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!("<Body was not UTF8: {:?}>", e)),
},
Err(e) => Cow::from(format!("<Failed to read body: {}>", e)),
})))
)
) as Box<Future<Item=_, Error=_>>
}
}
}))
}
fn required_octet_stream_put(&self, param_body: swagger::ByteArray, context: &C) -> Box<Future<Item=RequiredOctetStreamPutResponse, Error=ApiError>> { fn required_octet_stream_put(&self, param_body: swagger::ByteArray, context: &C) -> Box<Future<Item=RequiredOctetStreamPutResponse, Error=ApiError>> {
let mut uri = format!( let mut uri = format!(
@ -272,7 +420,6 @@ impl<F, C> Api<C> for Client<F> where
let mut request = hyper::Request::new(hyper::Method::Put, uri); let mut request = hyper::Request::new(hyper::Method::Put, uri);
// Body parameter
let body = param_body.0; let body = param_body.0;
request.set_body(body); request.set_body(body);

View File

@ -51,6 +51,18 @@ pub const BASE_PATH: &'static str = "";
pub const API_VERSION: &'static str = "1.0.7"; 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)] #[derive(Debug, PartialEq)]
pub enum RequiredOctetStreamPutResponse { pub enum RequiredOctetStreamPutResponse {
/// OK /// OK
@ -131,6 +143,12 @@ pub enum XmlPutResponse {
pub trait Api<C> { pub trait Api<C> {
fn multiple_auth_scheme_get(&self, context: &C) -> Box<Future<Item=MultipleAuthSchemeGetResponse, Error=ApiError>>;
fn readonly_auth_scheme_get(&self, context: &C) -> Box<Future<Item=ReadonlyAuthSchemeGetResponse, Error=ApiError>>;
fn required_octet_stream_put(&self, body: swagger::ByteArray, context: &C) -> Box<Future<Item=RequiredOctetStreamPutResponse, Error=ApiError>>; fn required_octet_stream_put(&self, body: swagger::ByteArray, context: &C) -> Box<Future<Item=RequiredOctetStreamPutResponse, Error=ApiError>>;
@ -160,6 +178,12 @@ pub trait Api<C> {
pub trait ApiNoContext { pub trait ApiNoContext {
fn multiple_auth_scheme_get(&self) -> Box<Future<Item=MultipleAuthSchemeGetResponse, Error=ApiError>>;
fn readonly_auth_scheme_get(&self) -> Box<Future<Item=ReadonlyAuthSchemeGetResponse, Error=ApiError>>;
fn required_octet_stream_put(&self, body: swagger::ByteArray) -> Box<Future<Item=RequiredOctetStreamPutResponse, Error=ApiError>>; fn required_octet_stream_put(&self, body: swagger::ByteArray) -> Box<Future<Item=RequiredOctetStreamPutResponse, Error=ApiError>>;
@ -200,6 +224,16 @@ impl<'a, T: Api<C> + Sized, C> ContextWrapperExt<'a, C> for T {
impl<'a, T: Api<C>, C> ApiNoContext for ContextWrapper<'a, T, C> { impl<'a, T: Api<C>, C> ApiNoContext for ContextWrapper<'a, T, C> {
fn multiple_auth_scheme_get(&self) -> Box<Future<Item=MultipleAuthSchemeGetResponse, Error=ApiError>> {
self.api().multiple_auth_scheme_get(&self.context())
}
fn readonly_auth_scheme_get(&self) -> Box<Future<Item=ReadonlyAuthSchemeGetResponse, Error=ApiError>> {
self.api().readonly_auth_scheme_get(&self.context())
}
fn required_octet_stream_put(&self, body: swagger::ByteArray) -> Box<Future<Item=RequiredOctetStreamPutResponse, Error=ApiError>> { fn required_octet_stream_put(&self, body: swagger::ByteArray) -> Box<Future<Item=RequiredOctetStreamPutResponse, Error=ApiError>> {
self.api().required_octet_stream_put(body, &self.context()) self.api().required_octet_stream_put(body, &self.context())
} }

View File

@ -83,6 +83,16 @@ impl<T, A, B, C, D> hyper::server::Service for AddContext<T, A>
fn call(&self, req: Self::Request) -> Self::Future { fn call(&self, req: Self::Request) -> Self::Future {
let context = A::default().push(XSpanIdString::get_or_generate(&req)); 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::<HyperAuth<Bearer>>().cloned() {
let auth_data = AuthData::Bearer(bearer.deref().clone());
let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>);
return self.inner.call((req, context));
}
}
let context = context.push(None::<AuthData>); let context = context.push(None::<AuthData>);
let context = context.push(None::<Authorization>); let context = context.push(None::<Authorization>);

View File

@ -35,6 +35,8 @@ use swagger::{ApiError, XSpanId, XSpanIdString, Has, RequestParser};
use swagger::auth::Scopes; use swagger::auth::Scopes;
use {Api, use {Api,
MultipleAuthSchemeGetResponse,
ReadonlyAuthSchemeGetResponse,
RequiredOctetStreamPutResponse, RequiredOctetStreamPutResponse,
ResponsesWithHeadersGetResponse, ResponsesWithHeadersGetResponse,
UuidGetResponse, UuidGetResponse,
@ -56,6 +58,8 @@ mod paths {
lazy_static! { lazy_static! {
pub static ref GLOBAL_REGEX_SET: regex::RegexSet = regex::RegexSet::new(vec![ pub static ref GLOBAL_REGEX_SET: regex::RegexSet = regex::RegexSet::new(vec![
r"^/multiple_auth_scheme$",
r"^/readonly_auth_scheme$",
r"^/required_octet_stream$", r"^/required_octet_stream$",
r"^/responses_with_headers$", r"^/responses_with_headers$",
r"^/uuid$", r"^/uuid$",
@ -64,12 +68,14 @@ mod paths {
r"^/xml_other$" r"^/xml_other$"
]).unwrap(); ]).unwrap();
} }
pub static ID_REQUIRED_OCTET_STREAM: usize = 0; pub static ID_MULTIPLE_AUTH_SCHEME: usize = 0;
pub static ID_RESPONSES_WITH_HEADERS: usize = 1; pub static ID_READONLY_AUTH_SCHEME: usize = 1;
pub static ID_UUID: usize = 2; pub static ID_REQUIRED_OCTET_STREAM: usize = 2;
pub static ID_XML: usize = 3; pub static ID_RESPONSES_WITH_HEADERS: usize = 3;
pub static ID_XML_EXTRA: usize = 4; pub static ID_UUID: usize = 4;
pub static ID_XML_OTHER: usize = 5; pub static ID_XML: usize = 5;
pub static ID_XML_EXTRA: usize = 6;
pub static ID_XML_OTHER: usize = 7;
} }
pub struct NewService<T, C> { pub struct NewService<T, C> {
@ -80,7 +86,7 @@ pub struct NewService<T, C> {
impl<T, C> NewService<T, C> impl<T, C> NewService<T, C>
where where
T: Api<C> + Clone + 'static, T: Api<C> + Clone + 'static,
C: Has<XSpanIdString> + 'static C: Has<XSpanIdString> + Has<Option<Authorization>> + 'static
{ {
pub fn new<U: Into<Arc<T>>>(api_impl: U) -> NewService<T, C> { pub fn new<U: Into<Arc<T>>>(api_impl: U) -> NewService<T, C> {
NewService{api_impl: api_impl.into(), marker: PhantomData} NewService{api_impl: api_impl.into(), marker: PhantomData}
@ -90,7 +96,7 @@ where
impl<T, C> hyper::server::NewService for NewService<T, C> impl<T, C> hyper::server::NewService for NewService<T, C>
where where
T: Api<C> + Clone + 'static, T: Api<C> + Clone + 'static,
C: Has<XSpanIdString> + 'static C: Has<XSpanIdString> + Has<Option<Authorization>> + 'static
{ {
type Request = (Request, C); type Request = (Request, C);
type Response = Response; type Response = Response;
@ -110,7 +116,7 @@ pub struct Service<T, C> {
impl<T, C> Service<T, C> impl<T, C> Service<T, C>
where where
T: Api<C> + Clone + 'static, T: Api<C> + Clone + 'static,
C: Has<XSpanIdString> + 'static { C: Has<XSpanIdString> + Has<Option<Authorization>> + 'static {
pub fn new<U: Into<Arc<T>>>(api_impl: U) -> Service<T, C> { pub fn new<U: Into<Arc<T>>>(api_impl: U) -> Service<T, C> {
Service{api_impl: api_impl.into(), marker: PhantomData} Service{api_impl: api_impl.into(), marker: PhantomData}
} }
@ -119,7 +125,7 @@ where
impl<T, C> hyper::server::Service for Service<T, C> impl<T, C> hyper::server::Service for Service<T, C>
where where
T: Api<C> + Clone + 'static, T: Api<C> + Clone + 'static,
C: Has<XSpanIdString> + 'static C: Has<XSpanIdString> + Has<Option<Authorization>> + 'static
{ {
type Request = (Request, C); type Request = (Request, C);
type Response = Response; type Response = Response;
@ -135,6 +141,127 @@ where
// Please update both places if changing how this code is autogenerated. // Please update both places if changing how this code is autogenerated.
match &method { match &method {
// MultipleAuthSchemeGet - GET /multiple_auth_scheme
&hyper::Method::Get if path.matched(paths::ID_MULTIPLE_AUTH_SCHEME) => {
{
let authorization = match (&context as &Has<Option<Authorization>>).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<String> = 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<XSpanIdString>).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<Future<Item=Response, Error=Error>>
},
// ReadonlyAuthSchemeGet - GET /readonly_auth_scheme
&hyper::Method::Get if path.matched(paths::ID_READONLY_AUTH_SCHEME) => {
{
let authorization = match (&context as &Has<Option<Authorization>>).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<String> = 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<XSpanIdString>).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<Future<Item=Response, Error=Error>>
},
// RequiredOctetStreamPut - PUT /required_octet_stream // RequiredOctetStreamPut - PUT /required_octet_stream
&hyper::Method::Put if path.matched(paths::ID_REQUIRED_OCTET_STREAM) => { &hyper::Method::Put if path.matched(paths::ID_REQUIRED_OCTET_STREAM) => {
// Body parameters (note that non-required body parameters will ignore garbage // Body parameters (note that non-required body parameters will ignore garbage
@ -644,6 +771,12 @@ impl RequestParser for ApiRequestParser {
let path = paths::GLOBAL_REGEX_SET.matches(request.uri().path()); let path = paths::GLOBAL_REGEX_SET.matches(request.uri().path());
match request.method() { 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 // RequiredOctetStreamPut - PUT /required_octet_stream
&hyper::Method::Put if path.matched(paths::ID_REQUIRED_OCTET_STREAM) => Ok("RequiredOctetStreamPut"), &hyper::Method::Put if path.matched(paths::ID_REQUIRED_OCTET_STREAM) => Ok("RequiredOctetStreamPut"),