Fix rust server auth (#18692)

* Added authentication via Bearer-token api_key and basic for server and client

* Improved errorhandling

* Added check on oAuth audience to example

* Updates of the petstore files for Rust-server

* Moved module import to prevent issue in callbacks

* updated samples

* Fix for unused-qualifications issue

* updated sampmles
This commit is contained in:
cvkem 2024-05-26 10:04:14 +02:00 committed by GitHub
parent 0daf9ffa5b
commit 5e8b589bea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
103 changed files with 2931 additions and 465 deletions

View File

@ -230,11 +230,15 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
supportingFiles.add(new SupportingFile("context.mustache", "src", "context.rs")); supportingFiles.add(new SupportingFile("context.mustache", "src", "context.rs"));
supportingFiles.add(new SupportingFile("models.mustache", "src", "models.rs")); supportingFiles.add(new SupportingFile("models.mustache", "src", "models.rs"));
supportingFiles.add(new SupportingFile("header.mustache", "src", "header.rs")); supportingFiles.add(new SupportingFile("header.mustache", "src", "header.rs"));
supportingFiles.add(new SupportingFile("auth.mustache", "src", "auth.rs"));
supportingFiles.add(new SupportingFile("server-mod.mustache", "src/server", "mod.rs")); supportingFiles.add(new SupportingFile("server-mod.mustache", "src/server", "mod.rs"));
supportingFiles.add(new SupportingFile("server-server_auth.mustache", "src/server", "server_auth.rs"));
supportingFiles.add(new SupportingFile("client-mod.mustache", "src/client", "mod.rs")); supportingFiles.add(new SupportingFile("client-mod.mustache", "src/client", "mod.rs"));
supportingFiles.add(new SupportingFile("example-server-main.mustache", "examples/server", "main.rs")); supportingFiles.add(new SupportingFile("example-server-main.mustache", "examples/server", "main.rs"));
supportingFiles.add(new SupportingFile("example-server-server.mustache", "examples/server", "server.rs")); supportingFiles.add(new SupportingFile("example-server-server.mustache", "examples/server", "server.rs"));
supportingFiles.add(new SupportingFile("example-server-auth.mustache", "examples/server", "server_auth.rs"));
supportingFiles.add(new SupportingFile("example-client-main.mustache", "examples/client", "main.rs")); supportingFiles.add(new SupportingFile("example-client-main.mustache", "examples/client", "main.rs"));
supportingFiles.add(new SupportingFile("example-client-auth.mustache", "examples/client", "client_auth.rs"));
supportingFiles.add(new SupportingFile("example-ca.pem", "examples", "ca.pem")); supportingFiles.add(new SupportingFile("example-ca.pem", "examples", "ca.pem"));
supportingFiles.add(new SupportingFile("example-server-chain.pem", "examples", "server-chain.pem")); supportingFiles.add(new SupportingFile("example-server-chain.pem", "examples", "server-chain.pem"));
supportingFiles.add(new SupportingFile("example-server-key.pem", "examples", "server-key.pem")); supportingFiles.add(new SupportingFile("example-server-key.pem", "examples", "server-key.pem"));

View File

@ -17,7 +17,7 @@ license = "{{.}}"
# Override this license by providing a License Object in the OpenAPI. # Override this license by providing a License Object in the OpenAPI.
license = "Unlicense" license = "Unlicense"
{{/licenseInfo}} {{/licenseInfo}}
edition = "2021" edition = "2018"
{{#publishRustRegistry}} {{#publishRustRegistry}}
publish = ["{{.}}"] publish = ["{{.}}"]
{{/publishRustRegistry}} {{/publishRustRegistry}}
@ -133,6 +133,9 @@ frunk_core = { version = "0.3.0", optional = true }
frunk-enum-derive = { version = "0.2.0", optional = true } frunk-enum-derive = { version = "0.2.0", optional = true }
frunk-enum-core = { version = "0.2.0", optional = true } frunk-enum-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies] [dev-dependencies]
clap = "2.25" clap = "2.25"
env_logger = "0.7" env_logger = "0.7"

View File

@ -0,0 +1,62 @@
use std::collections::BTreeSet;
use crate::server::Authorization;
use serde::{Deserialize, Serialize};
use swagger::{ApiError, auth::{Basic, Bearer}};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub iss: String,
pub aud: String,
pub company: String,
pub exp: u64,
pub scopes: String,
}
pub trait AuthenticationApi {
/// Method should be implemented (see example-code) to map Bearer-token to an Authorization
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map ApiKey to an Authorization
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map Basic (Username:password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError>;
}
// Implement it for AllowAllAuthenticator (dummy is needed, but should not used as we have Bearer authorization)
use swagger::auth::{AllowAllAuthenticator, RcBound, Scopes};
fn dummy_authorization() -> Authorization {
// Is called when MakeAllowAllAuthenticator is added to the stack. This is not needed as we have Bearer-authorization in the example-code.
// However, if you want to use it anyway this can not be unimplemented, so dummy implementation added.
// unimplemented!()
Authorization{
subject: "Dummmy".to_owned(),
scopes: Scopes::Some(BTreeSet::new()), // create an empty scope, as this should not be used
issuer: None
}
}
impl<T, RC> AuthenticationApi for AllowAllAuthenticator<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static {
/// Get method to map Bearer-token to an Authorization
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map api-key to an Authorization
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map basic token to an Authorization
fn basic_authorization(&self, _basic: &Basic) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
}

View File

@ -8,7 +8,8 @@ use std::marker::PhantomData;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::auth::{AuthData, Authorization, Bearer, Scopes}; use swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString}; use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api; use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> { pub struct MakeAddContext<T, A> {
inner: T, inner: T,
@ -89,7 +90,7 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
B: Push<Option<AuthData>, Result=C>, B: Push<Option<AuthData>, Result=C>,
C: Push<Option<Authorization>, Result=D>, C: Push<Option<Authorization>, Result=D>,
D: Send + 'static, D: Send + 'static,
T: Service<(Request<ReqBody>, D)> T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{ {
type Error = T::Error; type Error = T::Error;
type Future = T::Future; type Future = T::Future;
@ -111,9 +112,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
use swagger::auth::Basic; use swagger::auth::Basic;
use std::ops::Deref; use std::ops::Deref;
if let Some(basic) = swagger::auth::from_headers::<Basic>(headers) { if let Some(basic) = swagger::auth::from_headers::<Basic>(headers) {
let authorization = self.inner.basic_authorization(&basic);
let auth_data = AuthData::Basic(basic); let auth_data = AuthData::Basic(basic);
let context = context.push(Some(auth_data)); let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>); let context = match authorization {
Ok(auth) => context.push(Some(auth)),
Err(err) => {
error!("Error during Authorization: {err:?}");
context.push(None::<Authorization>)
}
};
return self.inner.call((request, context)) return self.inner.call((request, context))
} }
@ -124,9 +133,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
use swagger::auth::Bearer; use swagger::auth::Bearer;
use std::ops::Deref; use std::ops::Deref;
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) { if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
let authorization = self.inner.bearer_authorization(&bearer);
let auth_data = AuthData::Bearer(bearer); let auth_data = AuthData::Bearer(bearer);
let context = context.push(Some(auth_data)); let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>); let context = match authorization {
Ok(auth) => context.push(Some(auth)),
Err(err) => {
error!("Error during Authorization: {err:?}");
context.push(None::<Authorization>)
}
};
return self.inner.call((request, context)) return self.inner.call((request, context))
} }
@ -138,9 +155,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
use swagger::auth::Bearer; use swagger::auth::Bearer;
use std::ops::Deref; use std::ops::Deref;
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) { if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
let authorization = self.inner.bearer_authorization(&bearer);
let auth_data = AuthData::Bearer(bearer); let auth_data = AuthData::Bearer(bearer);
let context = context.push(Some(auth_data)); let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>); let context = match authorization {
Ok(auth) => context.push(Some(auth)),
Err(err) => {
error!("Error during Authorization: {err:?}");
context.push(None::<Authorization>)
}
};
return self.inner.call((request, context)) return self.inner.call((request, context))
} }
@ -152,9 +177,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
use swagger::auth::api_key_from_header; use swagger::auth::api_key_from_header;
if let Some(header) = api_key_from_header(headers, "{{{keyParamName}}}") { if let Some(header) = api_key_from_header(headers, "{{{keyParamName}}}") {
let authorization = self.inner.apikey_authorization(&header);
let auth_data = AuthData::ApiKey(header); let auth_data = AuthData::ApiKey(header);
let context = context.push(Some(auth_data)); let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>); let context = match authorization {
Ok(auth) => context.push(Some(auth)),
Err(err) => {
error!("Error during Authorization: {err:?}");
context.push(None::<Authorization>)
}
};
return self.inner.call((request, context)) return self.inner.call((request, context))
} }
@ -167,9 +200,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
.map(|e| e.1.clone().into_owned()) .map(|e| e.1.clone().into_owned())
.next(); .next();
if let Some(key) = key { if let Some(key) = key {
let authorization = self.inner.apikey_authorization(&key);
let auth_data = AuthData::ApiKey(key); let auth_data = AuthData::ApiKey(key);
let context = context.push(Some(auth_data)); let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>); let context = match authorization {
Ok(auth) => context.push(Some(auth)),
Err(err) => {
error!("Error during Authorization: {err:?}");
context.push(None::<Authorization>)
}
};
return self.inner.call((request, context)) return self.inner.call((request, context))
} }

View File

@ -0,0 +1,17 @@
use {{{externCrateName}}}::Claims;
use jsonwebtoken::{encode, errors::Error as JwtError, Algorithm, EncodingKey, Header};
use log::debug;
/// build an encrypted token with the provided claims.
pub fn build_token(my_claims: Claims, key: &[u8]) -> Result<String, JwtError> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header =
Header { kid: Some("signing_key".to_owned()), alg: Algorithm::HS512, ..Default::default() };
let token = encode(&header, &my_claims, &EncodingKey::from_secret(key))?;
debug!("Derived token: {:?}", token);
Ok(token)
}

View File

@ -7,7 +7,7 @@ mod server;
#[allow(unused_imports)] #[allow(unused_imports)]
use futures::{future, Stream, stream}; use futures::{future, Stream, stream};
#[allow(unused_imports)] #[allow(unused_imports)]
use {{{externCrateName}}}::{Api, ApiNoContext, Client, ContextWrapperExt, models, use {{{externCrateName}}}::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
{{#apiInfo}} {{#apiInfo}}
{{#apis}} {{#apis}}
{{#operations}} {{#operations}}
@ -20,6 +20,9 @@ use {{{externCrateName}}}::{Api, ApiNoContext, Client, ContextWrapperExt, models
}; };
use clap::{App, Arg}; use clap::{App, Arg};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
#[allow(unused_imports)] #[allow(unused_imports)]
use log::info; use log::info;
@ -29,6 +32,10 @@ use swagger::{AuthData, ContextBuilder, EmptyContext, Has, Push, XSpanIdString};
type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString); type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples // rt may be unused if there are no examples
#[allow(unused_mut)] #[allow(unused_mut)]
fn main() { fn main() {
@ -69,6 +76,37 @@ fn main() {
.help("Port to contact")) .help("Port to contact"))
.get_matches(); .get_matches();
// Create Bearer-token with a fixed key (secret) for test purposes.
// In a real (production) system this Bearer token should be obtained via an external Identity/Authentication-server
// Ensure that you set the correct algorithm and encodingkey that matches what is used on the server side.
// See https://github.com/Keats/jsonwebtoken for more information
let auth_token = build_token(
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "my_identity_provider".to_owned(),
// added a very long expiry time
aud: "org.acme.Resource_Server".to_string(),
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
{{#authMethods}}
{{#scopes}}
"{{{scope}}}",
{{/scopes}}
{{/authMethods}}
].join(", ")
},
b"secret").unwrap();
let auth_data = if !auth_token.is_empty() {
Some(AuthData::Bearer(swagger::auth::Bearer { token: auth_token}))
} else {
// No Bearer-token available, so return None
None
};
let is_https = matches.is_present("https"); let is_https = matches.is_present("https");
let base_url = format!("{}://{}:{}", let base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" }, if is_https { "https" } else { "http" },
@ -76,7 +114,7 @@ fn main() {
matches.value_of("port").unwrap()); matches.value_of("port").unwrap());
let context: ClientContext = let context: ClientContext =
swagger::make_context!(ContextBuilder, EmptyContext, None as Option<AuthData>, XSpanIdString::default()); swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default());
let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") { let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") {
// Using Simple HTTPS // Using Simple HTTPS

View File

@ -0,0 +1,132 @@
use swagger::{
ApiError,
auth::{Basic, Bearer},
Has,
XSpanIdString};
use {{{externCrateName}}}::{AuthenticationApi, Claims};
use crate::server::Server;
use jsonwebtoken::{decode, errors as JwtError, decode_header, DecodingKey, TokenData, Validation};
use swagger::auth::Authorization;
use log::{error, debug};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
/// Get a dummy claim with full permissions (all scopes) for testing purposes
fn full_permission_claim() -> Claims {
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "mini-bank-IDP".to_owned(),
aud: "org.acme.Resource_Server".to_string(),
// added a very long expiry time
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
{{#authMethods}}
{{#scopes}}
"{{{scope}}}",
{{/scopes}}
{{/authMethods}}
].join(", ")
}
}
/// Extract the data from a Bearer token using the provided Key (secret) and using the HS512-algorithm in this example.
fn extract_token_data(token: &str, key: &[u8]) -> Result<TokenData<Claims>, JwtError::Error> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header = decode_header(token)?;
let validation = {
let mut validation = Validation::new(header.alg);
validation.set_audience(&["org.acme.Resource_Server"]);
validation.validate_exp = true;
validation
};
let token_data = decode::<Claims>(
&token,
&DecodingKey::from_secret(key),
&validation,
)?;
Ok(token_data)
}
/// Build a swagger-Authorization based on the claims (Assuming claims have been extracted from a validated token)
fn build_authorization(claims: Claims) -> Authorization {
let mut scopes = std::collections::BTreeSet::<String>::new();
claims
.scopes
.split(",")
.map(|s| s.trim())
.for_each(|s| {let _ = scopes.insert(s.to_string()); });
let scopes = swagger::auth::Scopes::Some(scopes);
Authorization{
subject: claims.sub,
scopes,
issuer: Some(claims.iss)}
}
fn get_jwt_error_string(error: JwtError::Error) -> String {
match error.kind() {
JwtError::ErrorKind::InvalidSignature => "Incorrect token signature".to_owned(),
JwtError::ErrorKind::InvalidAlgorithm => "The Algorithm is not correct".to_owned(),
JwtError::ErrorKind::ExpiredSignature => "The token has expired".to_owned(),
JwtError::ErrorKind::Base64(e) => format!("Base64 decode failed: {e}"),
JwtError::ErrorKind::Json(e) => format!("JSON decoding: {e}"),
JwtError::ErrorKind::Utf8(e) => format!("Invalid UTF-8: {e}"),
_ => error.to_string()
}
}
impl<C> AuthenticationApi for Server<C> where C: Has<XSpanIdString> + Send + Sync {
/// Implementation of the method to map a Bearer-token to an Authorization
fn bearer_authorization(&self, bearer: &Bearer) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Bearer-token, {bearer:#?}");
match extract_token_data(&bearer.token, b"secret") {
Ok(auth_data) => {
debug!("\tUnpack auth_data as: {auth_data:#?}");
let authorization = build_authorization(auth_data.claims);
Ok(authorization)
},
Err(err) => {
let msg = get_jwt_error_string(err);
error!("Failed to unpack Bearer-token: {msg}");
Err(ApiError(msg))
}
}
}
/// Implementation of the method to map an api-key to an Authorization
fn apikey_authorization(&self, api_key: &str) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received api-key, {api_key:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
/// Implementation of the method to map a basic authentication (username and password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Basic-token, {basic:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
}

View File

@ -30,7 +30,9 @@ pub async fn create(addr: &str, https: bool) {
let service = MakeService::new(server); let service = MakeService::new(server);
let service = MakeAllowAllAuthenticator::new(service, "cosmo"); // This pushes a fourth layer of the middleware-stack even though Swagger assumes only three levels.
// This fourth layer creates an accept-all policy, hower the example-code already acchieves the same via a Bearer-token with full permissions, so next line is not needed (anymore).
// let service = MakeAllowAllAuthenticator::new(service, "cosmo");
#[allow(unused_mut)] #[allow(unused_mut)]
let mut service = let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build(); let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap(); let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop { loop {
if let Ok((tcp, _)) = tcp_listener.accept().await { if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap(); let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
} }
} }
} else { } else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP // Using HTTP
hyper::server::Server::bind(&addr).serve(service).await.unwrap() hyper::server::Server::bind(&addr).serve(service).await.unwrap()
} }

View File

@ -1,10 +1,13 @@
//! Main binary entry point for {{{externCrateName}}} implementation. //! Main binary entry point for {{{externCrateName}}} implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)] #![allow(missing_docs)]
use clap::{App, Arg}; use clap::{App, Arg};
mod server; mod server;
mod server_auth;
/// Create custom server, wire it to the autogenerated router, /// Create custom server, wire it to the autogenerated router,

View File

@ -14,5 +14,5 @@
context: &C) -> Result<{{{operationId}}}Response, ApiError> context: &C) -> Result<{{{operationId}}}Response, ApiError>
{ {
info!("{{#vendorExtensions}}{{{x-operation-id}}}{{/vendorExtensions}}({{#allParams}}{{#vendorExtensions}}{{{x-format-string}}}{{/vendorExtensions}}{{^-last}}, {{/-last}}{{/allParams}}) - X-Span-ID: {:?}"{{#allParams}}, {{{paramName}}}{{/allParams}}, context.get().0.clone()); info!("{{#vendorExtensions}}{{{x-operation-id}}}{{/vendorExtensions}}({{#allParams}}{{#vendorExtensions}}{{{x-format-string}}}{{/vendorExtensions}}{{^-last}}, {{/-last}}{{/allParams}}) - X-Span-ID: {:?}"{{#allParams}}, {{{paramName}}}{{/allParams}}, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }

View File

@ -1,5 +1,11 @@
{{>example-server-common}} {{>example-server-common}}
use jsonwebtoken::{decode, encode, errors::Error as JwtError, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize};
use swagger::auth::Authorization;
use crate::server_auth;
use {{{externCrateName}}}::{ use {{{externCrateName}}}::{
Api, Api,
{{#apiInfo}} {{#apiInfo}}

View File

@ -1,13 +1,16 @@
#![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)] #![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)]
#![allow(unused_imports, unused_attributes)] #![allow(unused_imports, unused_attributes)]
#![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names, clippy::too_many_arguments)] #![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names)]
use async_trait::async_trait; use async_trait::async_trait;
use futures::Stream; use futures::Stream;
use std::error::Error; use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper}; use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>; type ServiceError = Box<dyn Error + Send + Sync + 'static>;
@ -16,6 +19,10 @@ pub const BASE_PATH: &str = "{{{basePathWithoutHost}}}";
pub const API_VERSION: &str = "{{{.}}}"; pub const API_VERSION: &str = "{{{.}}}";
{{/appVersion}} {{/appVersion}}
mod auth;
pub use auth::{AuthenticationApi, Claims};
{{#apiInfo}} {{#apiInfo}}
{{#apis}} {{#apis}}
{{#operations}} {{#operations}}

View File

@ -23,8 +23,7 @@ use multipart::server::save::SaveResult;
{{/apiUsesMultipartFormData}} {{/apiUsesMultipartFormData}}
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::models; use crate::{models, header, AuthenticationApi};
use crate::header;
pub use crate::context; pub use crate::context;

View File

@ -1,3 +1,4 @@
pub struct MakeService<T, C> where pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static
@ -18,6 +19,7 @@ impl<T, C> MakeService<T, C> where
} }
} }
impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static

View File

@ -3,6 +3,8 @@ use crate::{Api{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}},
{{{operationId}}}Response{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}} {{{operationId}}}Response{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
}; };
mod server_auth;
{{#hasCallbacks}} {{#hasCallbacks}}
pub mod callbacks; pub mod callbacks;

View File

@ -224,11 +224,10 @@
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
{{/x-consumes-json}} {{/x-consumes-json}}
{{^x-consumes-plain-text}} {{^x-consumes-plain-text}}
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_{{{paramName}}}) => param_{{{paramName}}}, Ok(param_{{{paramName}}}) => param_{{{paramName}}},
{{#required}} {{#required}}
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
@ -422,11 +421,10 @@
Some("{{{contentType}}}") if param_{{{paramName}}}.is_none() => { Some("{{{contentType}}}") if param_{{{paramName}}}.is_none() => {
// Extract JSON part. // Extract JSON part.
let deserializer = &mut serde_json::Deserializer::from_slice(part.body.as_slice()); let deserializer = &mut serde_json::Deserializer::from_slice(part.body.as_slice());
let handle_unknown_field = |path: serde_ignored::Path<'_>| { let json_data: {{dataType}} = match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in JSON part: {}", path); warn!("Ignoring unknown field in JSON part: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
let json_data: {{dataType}} = match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(json_data) => json_data, Ok(json_data) => json_data,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)

View File

@ -0,0 +1,28 @@
use super::Service;
use crate::{Api, AuthenticationApi};
use swagger::{
ApiError,
Authorization,
auth::{Basic, Bearer},
Has,
XSpanIdString};
impl<T,C> AuthenticationApi for Service<T, C> where
T: Api<C> + Clone + Send + 'static + AuthenticationApi,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static {
/// Passthrough of the task to the api-implementation
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError> {
self.api_impl.bearer_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError> {
self.api_impl.apikey_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
self.api_impl.basic_authorization(basic)
}
}

View File

@ -8,14 +8,18 @@ docs/MultipartRequestObjectField.md
docs/MultipleIdenticalMimeTypesPostRequest.md docs/MultipleIdenticalMimeTypesPostRequest.md
docs/default_api.md docs/default_api.md
examples/ca.pem examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs examples/client/main.rs
examples/server-chain.pem examples/server-chain.pem
examples/server-key.pem examples/server-key.pem
examples/server/main.rs examples/server/main.rs
examples/server/server.rs examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs src/client/mod.rs
src/context.rs src/context.rs
src/header.rs src/header.rs
src/lib.rs src/lib.rs
src/models.rs src/models.rs
src/server/mod.rs src/server/mod.rs
src/server/server_auth.rs

View File

@ -5,7 +5,7 @@ authors = ["OpenAPI Generator team and contributors"]
description = "API under test" description = "API under test"
# Override this license by providing a License Object in the OpenAPI. # Override this license by providing a License Object in the OpenAPI.
license = "Unlicense" license = "Unlicense"
edition = "2021" edition = "2018"
[features] [features]
default = ["client", "server"] default = ["client", "server"]
@ -69,6 +69,9 @@ frunk_core = { version = "0.3.0", optional = true }
frunk-enum-derive = { version = "0.2.0", optional = true } frunk-enum-derive = { version = "0.2.0", optional = true }
frunk-enum-core = { version = "0.2.0", optional = true } frunk-enum-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies] [dev-dependencies]
clap = "2.25" clap = "2.25"
env_logger = "0.7" env_logger = "0.7"

View File

@ -0,0 +1,17 @@
use multipart_v3::Claims;
use jsonwebtoken::{encode, errors::Error as JwtError, Algorithm, EncodingKey, Header};
use log::debug;
/// build an encrypted token with the provided claims.
pub fn build_token(my_claims: Claims, key: &[u8]) -> Result<String, JwtError> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header =
Header { kid: Some("signing_key".to_owned()), alg: Algorithm::HS512, ..Default::default() };
let token = encode(&header, &my_claims, &EncodingKey::from_secret(key))?;
debug!("Derived token: {:?}", token);
Ok(token)
}

View File

@ -4,13 +4,16 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use futures::{future, Stream, stream}; use futures::{future, Stream, stream};
#[allow(unused_imports)] #[allow(unused_imports)]
use multipart_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models, use multipart_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
MultipartRelatedRequestPostResponse, MultipartRelatedRequestPostResponse,
MultipartRequestPostResponse, MultipartRequestPostResponse,
MultipleIdenticalMimeTypesPostResponse, MultipleIdenticalMimeTypesPostResponse,
}; };
use clap::{App, Arg}; use clap::{App, Arg};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
#[allow(unused_imports)] #[allow(unused_imports)]
use log::info; use log::info;
@ -20,6 +23,10 @@ use swagger::{AuthData, ContextBuilder, EmptyContext, Has, Push, XSpanIdString};
type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString); type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples // rt may be unused if there are no examples
#[allow(unused_mut)] #[allow(unused_mut)]
fn main() { fn main() {
@ -50,6 +57,32 @@ fn main() {
.help("Port to contact")) .help("Port to contact"))
.get_matches(); .get_matches();
// Create Bearer-token with a fixed key (secret) for test purposes.
// In a real (production) system this Bearer token should be obtained via an external Identity/Authentication-server
// Ensure that you set the correct algorithm and encodingkey that matches what is used on the server side.
// See https://github.com/Keats/jsonwebtoken for more information
let auth_token = build_token(
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "my_identity_provider".to_owned(),
// added a very long expiry time
aud: "org.acme.Resource_Server".to_string(),
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
].join(", ")
},
b"secret").unwrap();
let auth_data = if !auth_token.is_empty() {
Some(AuthData::Bearer(swagger::auth::Bearer { token: auth_token}))
} else {
// No Bearer-token available, so return None
None
};
let is_https = matches.is_present("https"); let is_https = matches.is_present("https");
let base_url = format!("{}://{}:{}", let base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" }, if is_https { "https" } else { "http" },
@ -57,7 +90,7 @@ fn main() {
matches.value_of("port").unwrap()); matches.value_of("port").unwrap());
let context: ClientContext = let context: ClientContext =
swagger::make_context!(ContextBuilder, EmptyContext, None as Option<AuthData>, XSpanIdString::default()); swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default());
let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") { let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") {
// Using Simple HTTPS // Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! Main binary entry point for multipart_v3 implementation. //! Main binary entry point for multipart_v3 implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)] #![allow(missing_docs)]
use clap::{App, Arg}; use clap::{App, Arg};
mod server; mod server;
mod server_auth;
/// Create custom server, wire it to the autogenerated router, /// Create custom server, wire it to the autogenerated router,

View File

@ -30,7 +30,9 @@ pub async fn create(addr: &str, https: bool) {
let service = MakeService::new(server); let service = MakeService::new(server);
let service = MakeAllowAllAuthenticator::new(service, "cosmo"); // This pushes a fourth layer of the middleware-stack even though Swagger assumes only three levels.
// This fourth layer creates an accept-all policy, hower the example-code already acchieves the same via a Bearer-token with full permissions, so next line is not needed (anymore).
// let service = MakeAllowAllAuthenticator::new(service, "cosmo");
#[allow(unused_mut)] #[allow(unused_mut)]
let mut service = let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build(); let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap(); let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop { loop {
if let Ok((tcp, _)) = tcp_listener.accept().await { if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap(); let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
} }
} }
} else { } else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP // Using HTTP
hyper::server::Server::bind(&addr).serve(service).await.unwrap() hyper::server::Server::bind(&addr).serve(service).await.unwrap()
} }
@ -92,6 +96,12 @@ impl<C> Server<C> {
} }
use jsonwebtoken::{decode, encode, errors::Error as JwtError, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize};
use swagger::auth::Authorization;
use crate::server_auth;
use multipart_v3::{ use multipart_v3::{
Api, Api,
MultipartRelatedRequestPostResponse, MultipartRelatedRequestPostResponse,
@ -113,7 +123,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MultipartRelatedRequestPostResponse, ApiError> context: &C) -> Result<MultipartRelatedRequestPostResponse, ApiError>
{ {
info!("multipart_related_request_post({:?}, {:?}, {:?}) - X-Span-ID: {:?}", required_binary_field, object_field, optional_binary_field, context.get().0.clone()); info!("multipart_related_request_post({:?}, {:?}, {:?}) - X-Span-ID: {:?}", required_binary_field, object_field, optional_binary_field, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn multipart_request_post( async fn multipart_request_post(
@ -125,7 +135,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MultipartRequestPostResponse, ApiError> context: &C) -> Result<MultipartRequestPostResponse, ApiError>
{ {
info!("multipart_request_post(\"{}\", {:?}, {:?}, {:?}) - X-Span-ID: {:?}", string_field, binary_field, optional_string_field, object_field, context.get().0.clone()); info!("multipart_request_post(\"{}\", {:?}, {:?}, {:?}) - X-Span-ID: {:?}", string_field, binary_field, optional_string_field, object_field, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn multiple_identical_mime_types_post( async fn multiple_identical_mime_types_post(
@ -135,7 +145,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MultipleIdenticalMimeTypesPostResponse, ApiError> context: &C) -> Result<MultipleIdenticalMimeTypesPostResponse, ApiError>
{ {
info!("multiple_identical_mime_types_post({:?}, {:?}) - X-Span-ID: {:?}", binary1, binary2, context.get().0.clone()); info!("multiple_identical_mime_types_post({:?}, {:?}) - X-Span-ID: {:?}", binary1, binary2, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
} }

View File

@ -0,0 +1,127 @@
use swagger::{
ApiError,
auth::{Basic, Bearer},
Has,
XSpanIdString};
use multipart_v3::{AuthenticationApi, Claims};
use crate::server::Server;
use jsonwebtoken::{decode, errors as JwtError, decode_header, DecodingKey, TokenData, Validation};
use swagger::auth::Authorization;
use log::{error, debug};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
/// Get a dummy claim with full permissions (all scopes) for testing purposes
fn full_permission_claim() -> Claims {
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "mini-bank-IDP".to_owned(),
aud: "org.acme.Resource_Server".to_string(),
// added a very long expiry time
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
].join(", ")
}
}
/// Extract the data from a Bearer token using the provided Key (secret) and using the HS512-algorithm in this example.
fn extract_token_data(token: &str, key: &[u8]) -> Result<TokenData<Claims>, JwtError::Error> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header = decode_header(token)?;
let validation = {
let mut validation = Validation::new(header.alg);
validation.set_audience(&["org.acme.Resource_Server"]);
validation.validate_exp = true;
validation
};
let token_data = decode::<Claims>(
&token,
&DecodingKey::from_secret(key),
&validation,
)?;
Ok(token_data)
}
/// Build a swagger-Authorization based on the claims (Assuming claims have been extracted from a validated token)
fn build_authorization(claims: Claims) -> Authorization {
let mut scopes = std::collections::BTreeSet::<String>::new();
claims
.scopes
.split(",")
.map(|s| s.trim())
.for_each(|s| {let _ = scopes.insert(s.to_string()); });
let scopes = swagger::auth::Scopes::Some(scopes);
Authorization{
subject: claims.sub,
scopes,
issuer: Some(claims.iss)}
}
fn get_jwt_error_string(error: JwtError::Error) -> String {
match error.kind() {
JwtError::ErrorKind::InvalidSignature => "Incorrect token signature".to_owned(),
JwtError::ErrorKind::InvalidAlgorithm => "The Algorithm is not correct".to_owned(),
JwtError::ErrorKind::ExpiredSignature => "The token has expired".to_owned(),
JwtError::ErrorKind::Base64(e) => format!("Base64 decode failed: {e}"),
JwtError::ErrorKind::Json(e) => format!("JSON decoding: {e}"),
JwtError::ErrorKind::Utf8(e) => format!("Invalid UTF-8: {e}"),
_ => error.to_string()
}
}
impl<C> AuthenticationApi for Server<C> where C: Has<XSpanIdString> + Send + Sync {
/// Implementation of the method to map a Bearer-token to an Authorization
fn bearer_authorization(&self, bearer: &Bearer) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Bearer-token, {bearer:#?}");
match extract_token_data(&bearer.token, b"secret") {
Ok(auth_data) => {
debug!("\tUnpack auth_data as: {auth_data:#?}");
let authorization = build_authorization(auth_data.claims);
Ok(authorization)
},
Err(err) => {
let msg = get_jwt_error_string(err);
error!("Failed to unpack Bearer-token: {msg}");
Err(ApiError(msg))
}
}
}
/// Implementation of the method to map an api-key to an Authorization
fn apikey_authorization(&self, api_key: &str) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received api-key, {api_key:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
/// Implementation of the method to map a basic authentication (username and password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Basic-token, {basic:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
}

View File

@ -0,0 +1,62 @@
use std::collections::BTreeSet;
use crate::server::Authorization;
use serde::{Deserialize, Serialize};
use swagger::{ApiError, auth::{Basic, Bearer}};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub iss: String,
pub aud: String,
pub company: String,
pub exp: u64,
pub scopes: String,
}
pub trait AuthenticationApi {
/// Method should be implemented (see example-code) to map Bearer-token to an Authorization
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map ApiKey to an Authorization
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map Basic (Username:password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError>;
}
// Implement it for AllowAllAuthenticator (dummy is needed, but should not used as we have Bearer authorization)
use swagger::auth::{AllowAllAuthenticator, RcBound, Scopes};
fn dummy_authorization() -> Authorization {
// Is called when MakeAllowAllAuthenticator is added to the stack. This is not needed as we have Bearer-authorization in the example-code.
// However, if you want to use it anyway this can not be unimplemented, so dummy implementation added.
// unimplemented!()
Authorization{
subject: "Dummmy".to_owned(),
scopes: Scopes::Some(BTreeSet::new()), // create an empty scope, as this should not be used
issuer: None
}
}
impl<T, RC> AuthenticationApi for AllowAllAuthenticator<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static {
/// Get method to map Bearer-token to an Authorization
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map api-key to an Authorization
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map basic token to an Authorization
fn basic_authorization(&self, _basic: &Basic) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
}

View File

@ -8,7 +8,8 @@ use std::marker::PhantomData;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::auth::{AuthData, Authorization, Bearer, Scopes}; use swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString}; use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api; use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> { pub struct MakeAddContext<T, A> {
inner: T, inner: T,
@ -89,7 +90,7 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
B: Push<Option<AuthData>, Result=C>, B: Push<Option<AuthData>, Result=C>,
C: Push<Option<Authorization>, Result=D>, C: Push<Option<Authorization>, Result=D>,
D: Send + 'static, D: Send + 'static,
T: Service<(Request<ReqBody>, D)> T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{ {
type Error = T::Error; type Error = T::Error;
type Future = T::Future; type Future = T::Future;

View File

@ -1,19 +1,26 @@
#![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)] #![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)]
#![allow(unused_imports, unused_attributes)] #![allow(unused_imports, unused_attributes)]
#![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names, clippy::too_many_arguments)] #![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names)]
use async_trait::async_trait; use async_trait::async_trait;
use futures::Stream; use futures::Stream;
use std::error::Error; use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper}; use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>; type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = ""; pub const BASE_PATH: &str = "";
pub const API_VERSION: &str = "1.0.7"; pub const API_VERSION: &str = "1.0.7";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum MultipartRelatedRequestPostResponse { pub enum MultipartRelatedRequestPostResponse {
/// OK /// OK

View File

@ -19,8 +19,7 @@ use multipart::server::Multipart;
use multipart::server::save::SaveResult; use multipart::server::save::SaveResult;
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::models; use crate::{models, header, AuthenticationApi};
use crate::header;
pub use crate::context; pub use crate::context;
@ -32,6 +31,8 @@ use crate::{Api,
MultipleIdenticalMimeTypesPostResponse MultipleIdenticalMimeTypesPostResponse
}; };
mod server_auth;
mod paths { mod paths {
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -48,6 +49,7 @@ mod paths {
pub(crate) static ID_MULTIPLE_IDENTICAL_MIME_TYPES: usize = 2; pub(crate) static ID_MULTIPLE_IDENTICAL_MIME_TYPES: usize = 2;
} }
pub struct MakeService<T, C> where pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Send + Sync + 'static C: Has<XSpanIdString> + Send + Sync + 'static
@ -68,6 +70,7 @@ impl<T, C> MakeService<T, C> where
} }
} }
impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Send + Sync + 'static C: Has<XSpanIdString> + Send + Sync + 'static
@ -206,11 +209,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
Some("application/json") if param_object_field.is_none() => { Some("application/json") if param_object_field.is_none() => {
// Extract JSON part. // Extract JSON part.
let deserializer = &mut serde_json::Deserializer::from_slice(part.body.as_slice()); let deserializer = &mut serde_json::Deserializer::from_slice(part.body.as_slice());
let handle_unknown_field = |path: serde_ignored::Path<'_>| { let json_data: models::MultipartRequestObjectField = match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in JSON part: {}", path); warn!("Ignoring unknown field in JSON part: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
let json_data: models::MultipartRequestObjectField = match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(json_data) => json_data, Ok(json_data) => json_data,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)

View File

@ -0,0 +1,28 @@
use super::Service;
use crate::{Api, AuthenticationApi};
use swagger::{
ApiError,
Authorization,
auth::{Basic, Bearer},
Has,
XSpanIdString};
impl<T,C> AuthenticationApi for Service<T, C> where
T: Api<C> + Clone + Send + 'static + AuthenticationApi,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static {
/// Passthrough of the task to the api-implementation
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError> {
self.api_impl.bearer_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError> {
self.api_impl.apikey_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
self.api_impl.basic_authorization(basic)
}
}

View File

@ -6,14 +6,18 @@ api/openapi.yaml
docs/OpGetRequest.md docs/OpGetRequest.md
docs/default_api.md docs/default_api.md
examples/ca.pem examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs examples/client/main.rs
examples/server-chain.pem examples/server-chain.pem
examples/server-key.pem examples/server-key.pem
examples/server/main.rs examples/server/main.rs
examples/server/server.rs examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs src/client/mod.rs
src/context.rs src/context.rs
src/header.rs src/header.rs
src/lib.rs src/lib.rs
src/models.rs src/models.rs
src/server/mod.rs src/server/mod.rs
src/server/server_auth.rs

View File

@ -5,7 +5,7 @@ authors = ["OpenAPI Generator team and contributors"]
description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)" description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)"
# Override this license by providing a License Object in the OpenAPI. # Override this license by providing a License Object in the OpenAPI.
license = "Unlicense" license = "Unlicense"
edition = "2021" edition = "2018"
[features] [features]
default = ["client", "server"] default = ["client", "server"]
@ -59,6 +59,9 @@ frunk_core = { version = "0.3.0", optional = true }
frunk-enum-derive = { version = "0.2.0", optional = true } frunk-enum-derive = { version = "0.2.0", optional = true }
frunk-enum-core = { version = "0.2.0", optional = true } frunk-enum-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies] [dev-dependencies]
clap = "2.25" clap = "2.25"
env_logger = "0.7" env_logger = "0.7"

View File

@ -0,0 +1,17 @@
use no_example_v3::Claims;
use jsonwebtoken::{encode, errors::Error as JwtError, Algorithm, EncodingKey, Header};
use log::debug;
/// build an encrypted token with the provided claims.
pub fn build_token(my_claims: Claims, key: &[u8]) -> Result<String, JwtError> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header =
Header { kid: Some("signing_key".to_owned()), alg: Algorithm::HS512, ..Default::default() };
let token = encode(&header, &my_claims, &EncodingKey::from_secret(key))?;
debug!("Derived token: {:?}", token);
Ok(token)
}

View File

@ -4,11 +4,14 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use futures::{future, Stream, stream}; use futures::{future, Stream, stream};
#[allow(unused_imports)] #[allow(unused_imports)]
use no_example_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models, use no_example_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
OpGetResponse, OpGetResponse,
}; };
use clap::{App, Arg}; use clap::{App, Arg};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
#[allow(unused_imports)] #[allow(unused_imports)]
use log::info; use log::info;
@ -18,6 +21,10 @@ use swagger::{AuthData, ContextBuilder, EmptyContext, Has, Push, XSpanIdString};
type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString); type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples // rt may be unused if there are no examples
#[allow(unused_mut)] #[allow(unused_mut)]
fn main() { fn main() {
@ -45,6 +52,32 @@ fn main() {
.help("Port to contact")) .help("Port to contact"))
.get_matches(); .get_matches();
// Create Bearer-token with a fixed key (secret) for test purposes.
// In a real (production) system this Bearer token should be obtained via an external Identity/Authentication-server
// Ensure that you set the correct algorithm and encodingkey that matches what is used on the server side.
// See https://github.com/Keats/jsonwebtoken for more information
let auth_token = build_token(
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "my_identity_provider".to_owned(),
// added a very long expiry time
aud: "org.acme.Resource_Server".to_string(),
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
].join(", ")
},
b"secret").unwrap();
let auth_data = if !auth_token.is_empty() {
Some(AuthData::Bearer(swagger::auth::Bearer { token: auth_token}))
} else {
// No Bearer-token available, so return None
None
};
let is_https = matches.is_present("https"); let is_https = matches.is_present("https");
let base_url = format!("{}://{}:{}", let base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" }, if is_https { "https" } else { "http" },
@ -52,7 +85,7 @@ fn main() {
matches.value_of("port").unwrap()); matches.value_of("port").unwrap());
let context: ClientContext = let context: ClientContext =
swagger::make_context!(ContextBuilder, EmptyContext, None as Option<AuthData>, XSpanIdString::default()); swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default());
let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") { let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") {
// Using Simple HTTPS // Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! Main binary entry point for no_example_v3 implementation. //! Main binary entry point for no_example_v3 implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)] #![allow(missing_docs)]
use clap::{App, Arg}; use clap::{App, Arg};
mod server; mod server;
mod server_auth;
/// Create custom server, wire it to the autogenerated router, /// Create custom server, wire it to the autogenerated router,

View File

@ -30,7 +30,9 @@ pub async fn create(addr: &str, https: bool) {
let service = MakeService::new(server); let service = MakeService::new(server);
let service = MakeAllowAllAuthenticator::new(service, "cosmo"); // This pushes a fourth layer of the middleware-stack even though Swagger assumes only three levels.
// This fourth layer creates an accept-all policy, hower the example-code already acchieves the same via a Bearer-token with full permissions, so next line is not needed (anymore).
// let service = MakeAllowAllAuthenticator::new(service, "cosmo");
#[allow(unused_mut)] #[allow(unused_mut)]
let mut service = let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build(); let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap(); let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop { loop {
if let Ok((tcp, _)) = tcp_listener.accept().await { if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap(); let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
} }
} }
} else { } else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP // Using HTTP
hyper::server::Server::bind(&addr).serve(service).await.unwrap() hyper::server::Server::bind(&addr).serve(service).await.unwrap()
} }
@ -92,6 +96,12 @@ impl<C> Server<C> {
} }
use jsonwebtoken::{decode, encode, errors::Error as JwtError, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize};
use swagger::auth::Authorization;
use crate::server_auth;
use no_example_v3::{ use no_example_v3::{
Api, Api,
OpGetResponse, OpGetResponse,
@ -109,7 +119,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<OpGetResponse, ApiError> context: &C) -> Result<OpGetResponse, ApiError>
{ {
info!("op_get({:?}) - X-Span-ID: {:?}", op_get_request, context.get().0.clone()); info!("op_get({:?}) - X-Span-ID: {:?}", op_get_request, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
} }

View File

@ -0,0 +1,127 @@
use swagger::{
ApiError,
auth::{Basic, Bearer},
Has,
XSpanIdString};
use no_example_v3::{AuthenticationApi, Claims};
use crate::server::Server;
use jsonwebtoken::{decode, errors as JwtError, decode_header, DecodingKey, TokenData, Validation};
use swagger::auth::Authorization;
use log::{error, debug};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
/// Get a dummy claim with full permissions (all scopes) for testing purposes
fn full_permission_claim() -> Claims {
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "mini-bank-IDP".to_owned(),
aud: "org.acme.Resource_Server".to_string(),
// added a very long expiry time
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
].join(", ")
}
}
/// Extract the data from a Bearer token using the provided Key (secret) and using the HS512-algorithm in this example.
fn extract_token_data(token: &str, key: &[u8]) -> Result<TokenData<Claims>, JwtError::Error> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header = decode_header(token)?;
let validation = {
let mut validation = Validation::new(header.alg);
validation.set_audience(&["org.acme.Resource_Server"]);
validation.validate_exp = true;
validation
};
let token_data = decode::<Claims>(
&token,
&DecodingKey::from_secret(key),
&validation,
)?;
Ok(token_data)
}
/// Build a swagger-Authorization based on the claims (Assuming claims have been extracted from a validated token)
fn build_authorization(claims: Claims) -> Authorization {
let mut scopes = std::collections::BTreeSet::<String>::new();
claims
.scopes
.split(",")
.map(|s| s.trim())
.for_each(|s| {let _ = scopes.insert(s.to_string()); });
let scopes = swagger::auth::Scopes::Some(scopes);
Authorization{
subject: claims.sub,
scopes,
issuer: Some(claims.iss)}
}
fn get_jwt_error_string(error: JwtError::Error) -> String {
match error.kind() {
JwtError::ErrorKind::InvalidSignature => "Incorrect token signature".to_owned(),
JwtError::ErrorKind::InvalidAlgorithm => "The Algorithm is not correct".to_owned(),
JwtError::ErrorKind::ExpiredSignature => "The token has expired".to_owned(),
JwtError::ErrorKind::Base64(e) => format!("Base64 decode failed: {e}"),
JwtError::ErrorKind::Json(e) => format!("JSON decoding: {e}"),
JwtError::ErrorKind::Utf8(e) => format!("Invalid UTF-8: {e}"),
_ => error.to_string()
}
}
impl<C> AuthenticationApi for Server<C> where C: Has<XSpanIdString> + Send + Sync {
/// Implementation of the method to map a Bearer-token to an Authorization
fn bearer_authorization(&self, bearer: &Bearer) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Bearer-token, {bearer:#?}");
match extract_token_data(&bearer.token, b"secret") {
Ok(auth_data) => {
debug!("\tUnpack auth_data as: {auth_data:#?}");
let authorization = build_authorization(auth_data.claims);
Ok(authorization)
},
Err(err) => {
let msg = get_jwt_error_string(err);
error!("Failed to unpack Bearer-token: {msg}");
Err(ApiError(msg))
}
}
}
/// Implementation of the method to map an api-key to an Authorization
fn apikey_authorization(&self, api_key: &str) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received api-key, {api_key:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
/// Implementation of the method to map a basic authentication (username and password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Basic-token, {basic:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
}

View File

@ -0,0 +1,62 @@
use std::collections::BTreeSet;
use crate::server::Authorization;
use serde::{Deserialize, Serialize};
use swagger::{ApiError, auth::{Basic, Bearer}};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub iss: String,
pub aud: String,
pub company: String,
pub exp: u64,
pub scopes: String,
}
pub trait AuthenticationApi {
/// Method should be implemented (see example-code) to map Bearer-token to an Authorization
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map ApiKey to an Authorization
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map Basic (Username:password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError>;
}
// Implement it for AllowAllAuthenticator (dummy is needed, but should not used as we have Bearer authorization)
use swagger::auth::{AllowAllAuthenticator, RcBound, Scopes};
fn dummy_authorization() -> Authorization {
// Is called when MakeAllowAllAuthenticator is added to the stack. This is not needed as we have Bearer-authorization in the example-code.
// However, if you want to use it anyway this can not be unimplemented, so dummy implementation added.
// unimplemented!()
Authorization{
subject: "Dummmy".to_owned(),
scopes: Scopes::Some(BTreeSet::new()), // create an empty scope, as this should not be used
issuer: None
}
}
impl<T, RC> AuthenticationApi for AllowAllAuthenticator<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static {
/// Get method to map Bearer-token to an Authorization
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map api-key to an Authorization
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map basic token to an Authorization
fn basic_authorization(&self, _basic: &Basic) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
}

View File

@ -8,7 +8,8 @@ use std::marker::PhantomData;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::auth::{AuthData, Authorization, Bearer, Scopes}; use swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString}; use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api; use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> { pub struct MakeAddContext<T, A> {
inner: T, inner: T,
@ -89,7 +90,7 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
B: Push<Option<AuthData>, Result=C>, B: Push<Option<AuthData>, Result=C>,
C: Push<Option<Authorization>, Result=D>, C: Push<Option<Authorization>, Result=D>,
D: Send + 'static, D: Send + 'static,
T: Service<(Request<ReqBody>, D)> T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{ {
type Error = T::Error; type Error = T::Error;
type Future = T::Future; type Future = T::Future;

View File

@ -1,19 +1,26 @@
#![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)] #![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)]
#![allow(unused_imports, unused_attributes)] #![allow(unused_imports, unused_attributes)]
#![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names, clippy::too_many_arguments)] #![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names)]
use async_trait::async_trait; use async_trait::async_trait;
use futures::Stream; use futures::Stream;
use std::error::Error; use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper}; use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>; type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = ""; pub const BASE_PATH: &str = "";
pub const API_VERSION: &str = "0.0.1"; pub const API_VERSION: &str = "0.0.1";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum OpGetResponse { pub enum OpGetResponse {
/// OK /// OK

View File

@ -14,8 +14,7 @@ use swagger::auth::Scopes;
use url::form_urlencoded; use url::form_urlencoded;
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::models; use crate::{models, header, AuthenticationApi};
use crate::header;
pub use crate::context; pub use crate::context;
@ -25,6 +24,8 @@ use crate::{Api,
OpGetResponse OpGetResponse
}; };
mod server_auth;
mod paths { mod paths {
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -37,6 +38,7 @@ mod paths {
pub(crate) static ID_OP: usize = 0; pub(crate) static ID_OP: usize = 0;
} }
pub struct MakeService<T, C> where pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Send + Sync + 'static C: Has<XSpanIdString> + Send + Sync + 'static
@ -57,6 +59,7 @@ impl<T, C> MakeService<T, C> where
} }
} }
impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Send + Sync + 'static C: Has<XSpanIdString> + Send + Sync + 'static
@ -150,11 +153,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_op_get_request: Option<models::OpGetRequest> = if !body.is_empty() { let param_op_get_request: Option<models::OpGetRequest> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_op_get_request) => param_op_get_request, Ok(param_op_get_request) => param_op_get_request,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)

View File

@ -0,0 +1,28 @@
use super::Service;
use crate::{Api, AuthenticationApi};
use swagger::{
ApiError,
Authorization,
auth::{Basic, Bearer},
Has,
XSpanIdString};
impl<T,C> AuthenticationApi for Service<T, C> where
T: Api<C> + Clone + Send + 'static + AuthenticationApi,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static {
/// Passthrough of the task to the api-implementation
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError> {
self.api_impl.bearer_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError> {
self.api_impl.apikey_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
self.api_impl.basic_authorization(basic)
}
}

View File

@ -37,12 +37,15 @@ docs/XmlObject.md
docs/default_api.md docs/default_api.md
docs/repo_api.md docs/repo_api.md
examples/ca.pem examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs examples/client/main.rs
examples/client/server.rs examples/client/server.rs
examples/server-chain.pem examples/server-chain.pem
examples/server-key.pem examples/server-key.pem
examples/server/main.rs examples/server/main.rs
examples/server/server.rs examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/callbacks.rs src/client/callbacks.rs
src/client/mod.rs src/client/mod.rs
src/context.rs src/context.rs
@ -51,3 +54,4 @@ src/lib.rs
src/models.rs src/models.rs
src/server/callbacks.rs src/server/callbacks.rs
src/server/mod.rs src/server/mod.rs
src/server/server_auth.rs

View File

@ -5,7 +5,7 @@ authors = ["OpenAPI Generator team and contributors"]
description = "API under test" description = "API under test"
# Override this license by providing a License Object in the OpenAPI. # Override this license by providing a License Object in the OpenAPI.
license = "Unlicense" license = "Unlicense"
edition = "2021" edition = "2018"
[features] [features]
default = ["client", "server"] default = ["client", "server"]
@ -65,6 +65,9 @@ frunk_core = { version = "0.3.0", optional = true }
frunk-enum-derive = { version = "0.2.0", optional = true } frunk-enum-derive = { version = "0.2.0", optional = true }
frunk-enum-core = { version = "0.2.0", optional = true } frunk-enum-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies] [dev-dependencies]
clap = "2.25" clap = "2.25"
env_logger = "0.7" env_logger = "0.7"

View File

@ -0,0 +1,17 @@
use openapi_v3::Claims;
use jsonwebtoken::{encode, errors::Error as JwtError, Algorithm, EncodingKey, Header};
use log::debug;
/// build an encrypted token with the provided claims.
pub fn build_token(my_claims: Claims, key: &[u8]) -> Result<String, JwtError> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header =
Header { kid: Some("signing_key".to_owned()), alg: Algorithm::HS512, ..Default::default() };
let token = encode(&header, &my_claims, &EncodingKey::from_secret(key))?;
debug!("Derived token: {:?}", token);
Ok(token)
}

View File

@ -5,7 +5,7 @@ mod server;
#[allow(unused_imports)] #[allow(unused_imports)]
use futures::{future, Stream, stream}; use futures::{future, Stream, stream};
#[allow(unused_imports)] #[allow(unused_imports)]
use openapi_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models, use openapi_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
AnyOfGetResponse, AnyOfGetResponse,
CallbackWithHeaderPostResponse, CallbackWithHeaderPostResponse,
ComplexQueryParamGetResponse, ComplexQueryParamGetResponse,
@ -35,6 +35,9 @@ use openapi_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models,
}; };
use clap::{App, Arg}; use clap::{App, Arg};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
#[allow(unused_imports)] #[allow(unused_imports)]
use log::info; use log::info;
@ -44,6 +47,10 @@ use swagger::{AuthData, ContextBuilder, EmptyContext, Has, Push, XSpanIdString};
type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString); type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples // rt may be unused if there are no examples
#[allow(unused_mut)] #[allow(unused_mut)]
fn main() { fn main() {
@ -96,6 +103,34 @@ fn main() {
.help("Port to contact")) .help("Port to contact"))
.get_matches(); .get_matches();
// Create Bearer-token with a fixed key (secret) for test purposes.
// In a real (production) system this Bearer token should be obtained via an external Identity/Authentication-server
// Ensure that you set the correct algorithm and encodingkey that matches what is used on the server side.
// See https://github.com/Keats/jsonwebtoken for more information
let auth_token = build_token(
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "my_identity_provider".to_owned(),
// added a very long expiry time
aud: "org.acme.Resource_Server".to_string(),
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
"test.read",
"test.write",
].join(", ")
},
b"secret").unwrap();
let auth_data = if !auth_token.is_empty() {
Some(AuthData::Bearer(swagger::auth::Bearer { token: auth_token}))
} else {
// No Bearer-token available, so return None
None
};
let is_https = matches.is_present("https"); let is_https = matches.is_present("https");
let base_url = format!("{}://{}:{}", let base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" }, if is_https { "https" } else { "http" },
@ -103,7 +138,7 @@ fn main() {
matches.value_of("port").unwrap()); matches.value_of("port").unwrap());
let context: ClientContext = let context: ClientContext =
swagger::make_context!(ContextBuilder, EmptyContext, None as Option<AuthData>, XSpanIdString::default()); swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default());
let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") { let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") {
// Using Simple HTTPS // Using Simple HTTPS

View File

@ -30,7 +30,9 @@ pub async fn create(addr: &str, https: bool) {
let service = MakeService::new(server); let service = MakeService::new(server);
let service = MakeAllowAllAuthenticator::new(service, "cosmo"); // This pushes a fourth layer of the middleware-stack even though Swagger assumes only three levels.
// This fourth layer creates an accept-all policy, hower the example-code already acchieves the same via a Bearer-token with full permissions, so next line is not needed (anymore).
// let service = MakeAllowAllAuthenticator::new(service, "cosmo");
#[allow(unused_mut)] #[allow(unused_mut)]
let mut service = let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build(); let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap(); let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop { loop {
if let Ok((tcp, _)) = tcp_listener.accept().await { if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap(); let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
} }
} }
} else { } else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP // Using HTTP
hyper::server::Server::bind(&addr).serve(service).await.unwrap() hyper::server::Server::bind(&addr).serve(service).await.unwrap()
} }
@ -108,7 +112,7 @@ impl<C> CallbackApi<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CallbackCallbackWithHeaderPostResponse, ApiError> context: &C) -> Result<CallbackCallbackWithHeaderPostResponse, ApiError>
{ {
info!("callback_callback_with_header_post({:?}) - X-Span-ID: {:?}", information, context.get().0.clone()); info!("callback_callback_with_header_post({:?}) - X-Span-ID: {:?}", information, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn callback_callback_post( async fn callback_callback_post(
@ -117,7 +121,7 @@ impl<C> CallbackApi<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CallbackCallbackPostResponse, ApiError> context: &C) -> Result<CallbackCallbackPostResponse, ApiError>
{ {
info!("callback_callback_post() - X-Span-ID: {:?}", context.get().0.clone()); info!("callback_callback_post() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
} }

View File

@ -1,10 +1,13 @@
//! Main binary entry point for openapi_v3 implementation. //! Main binary entry point for openapi_v3 implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)] #![allow(missing_docs)]
use clap::{App, Arg}; use clap::{App, Arg};
mod server; mod server;
mod server_auth;
/// Create custom server, wire it to the autogenerated router, /// Create custom server, wire it to the autogenerated router,

View File

@ -30,7 +30,9 @@ pub async fn create(addr: &str, https: bool) {
let service = MakeService::new(server); let service = MakeService::new(server);
let service = MakeAllowAllAuthenticator::new(service, "cosmo"); // This pushes a fourth layer of the middleware-stack even though Swagger assumes only three levels.
// This fourth layer creates an accept-all policy, hower the example-code already acchieves the same via a Bearer-token with full permissions, so next line is not needed (anymore).
// let service = MakeAllowAllAuthenticator::new(service, "cosmo");
#[allow(unused_mut)] #[allow(unused_mut)]
let mut service = let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build(); let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap(); let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop { loop {
if let Ok((tcp, _)) = tcp_listener.accept().await { if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap(); let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
} }
} }
} else { } else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP // Using HTTP
hyper::server::Server::bind(&addr).serve(service).await.unwrap() hyper::server::Server::bind(&addr).serve(service).await.unwrap()
} }
@ -92,6 +96,12 @@ impl<C> Server<C> {
} }
use jsonwebtoken::{decode, encode, errors::Error as JwtError, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize};
use swagger::auth::Authorization;
use crate::server_auth;
use openapi_v3::{ use openapi_v3::{
Api, Api,
AnyOfGetResponse, AnyOfGetResponse,
@ -134,7 +144,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<AnyOfGetResponse, ApiError> context: &C) -> Result<AnyOfGetResponse, ApiError>
{ {
info!("any_of_get({:?}) - X-Span-ID: {:?}", any_of, context.get().0.clone()); info!("any_of_get({:?}) - X-Span-ID: {:?}", any_of, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn callback_with_header_post( async fn callback_with_header_post(
@ -143,7 +153,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CallbackWithHeaderPostResponse, ApiError> context: &C) -> Result<CallbackWithHeaderPostResponse, ApiError>
{ {
info!("callback_with_header_post(\"{}\") - X-Span-ID: {:?}", url, context.get().0.clone()); info!("callback_with_header_post(\"{}\") - X-Span-ID: {:?}", url, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn complex_query_param_get( async fn complex_query_param_get(
@ -152,7 +162,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<ComplexQueryParamGetResponse, ApiError> context: &C) -> Result<ComplexQueryParamGetResponse, ApiError>
{ {
info!("complex_query_param_get({:?}) - X-Span-ID: {:?}", list_of_strings, context.get().0.clone()); info!("complex_query_param_get({:?}) - X-Span-ID: {:?}", list_of_strings, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn enum_in_path_path_param_get( async fn enum_in_path_path_param_get(
@ -161,7 +171,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<EnumInPathPathParamGetResponse, ApiError> context: &C) -> Result<EnumInPathPathParamGetResponse, ApiError>
{ {
info!("enum_in_path_path_param_get({:?}) - X-Span-ID: {:?}", path_param, context.get().0.clone()); info!("enum_in_path_path_param_get({:?}) - X-Span-ID: {:?}", path_param, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn json_complex_query_param_get( async fn json_complex_query_param_get(
@ -170,7 +180,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<JsonComplexQueryParamGetResponse, ApiError> context: &C) -> Result<JsonComplexQueryParamGetResponse, ApiError>
{ {
info!("json_complex_query_param_get({:?}) - X-Span-ID: {:?}", list_of_strings, context.get().0.clone()); info!("json_complex_query_param_get({:?}) - X-Span-ID: {:?}", list_of_strings, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn mandatory_request_header_get( async fn mandatory_request_header_get(
@ -179,7 +189,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MandatoryRequestHeaderGetResponse, ApiError> context: &C) -> Result<MandatoryRequestHeaderGetResponse, ApiError>
{ {
info!("mandatory_request_header_get(\"{}\") - X-Span-ID: {:?}", x_header, context.get().0.clone()); info!("mandatory_request_header_get(\"{}\") - X-Span-ID: {:?}", x_header, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn merge_patch_json_get( async fn merge_patch_json_get(
@ -187,7 +197,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MergePatchJsonGetResponse, ApiError> context: &C) -> Result<MergePatchJsonGetResponse, ApiError>
{ {
info!("merge_patch_json_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("merge_patch_json_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Get some stuff. /// Get some stuff.
@ -196,7 +206,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MultigetGetResponse, ApiError> context: &C) -> Result<MultigetGetResponse, ApiError>
{ {
info!("multiget_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("multiget_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn multiple_auth_scheme_get( async fn multiple_auth_scheme_get(
@ -204,7 +214,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MultipleAuthSchemeGetResponse, ApiError> context: &C) -> Result<MultipleAuthSchemeGetResponse, ApiError>
{ {
info!("multiple_auth_scheme_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("multiple_auth_scheme_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn one_of_get( async fn one_of_get(
@ -212,7 +222,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<OneOfGetResponse, ApiError> context: &C) -> Result<OneOfGetResponse, ApiError>
{ {
info!("one_of_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("one_of_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn override_server_get( async fn override_server_get(
@ -220,7 +230,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<OverrideServerGetResponse, ApiError> context: &C) -> Result<OverrideServerGetResponse, ApiError>
{ {
info!("override_server_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("override_server_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Get some stuff with parameters. /// Get some stuff with parameters.
@ -232,7 +242,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<ParamgetGetResponse, ApiError> context: &C) -> Result<ParamgetGetResponse, ApiError>
{ {
info!("paramget_get({:?}, {:?}, {:?}) - X-Span-ID: {:?}", uuid, some_object, some_list, context.get().0.clone()); info!("paramget_get({:?}, {:?}, {:?}) - X-Span-ID: {:?}", uuid, some_object, some_list, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn readonly_auth_scheme_get( async fn readonly_auth_scheme_get(
@ -240,7 +250,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<ReadonlyAuthSchemeGetResponse, ApiError> context: &C) -> Result<ReadonlyAuthSchemeGetResponse, ApiError>
{ {
info!("readonly_auth_scheme_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("readonly_auth_scheme_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn register_callback_post( async fn register_callback_post(
@ -249,7 +259,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<RegisterCallbackPostResponse, ApiError> context: &C) -> Result<RegisterCallbackPostResponse, ApiError>
{ {
info!("register_callback_post(\"{}\") - X-Span-ID: {:?}", url, context.get().0.clone()); info!("register_callback_post(\"{}\") - X-Span-ID: {:?}", url, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn required_octet_stream_put( async fn required_octet_stream_put(
@ -258,7 +268,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<RequiredOctetStreamPutResponse, ApiError> context: &C) -> Result<RequiredOctetStreamPutResponse, ApiError>
{ {
info!("required_octet_stream_put({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("required_octet_stream_put({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn responses_with_headers_get( async fn responses_with_headers_get(
@ -266,7 +276,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<ResponsesWithHeadersGetResponse, ApiError> context: &C) -> Result<ResponsesWithHeadersGetResponse, ApiError>
{ {
info!("responses_with_headers_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("responses_with_headers_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn rfc7807_get( async fn rfc7807_get(
@ -274,7 +284,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Rfc7807GetResponse, ApiError> context: &C) -> Result<Rfc7807GetResponse, ApiError>
{ {
info!("rfc7807_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("rfc7807_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn untyped_property_get( async fn untyped_property_get(
@ -283,7 +293,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UntypedPropertyGetResponse, ApiError> context: &C) -> Result<UntypedPropertyGetResponse, ApiError>
{ {
info!("untyped_property_get({:?}) - X-Span-ID: {:?}", object_untyped_props, context.get().0.clone()); info!("untyped_property_get({:?}) - X-Span-ID: {:?}", object_untyped_props, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn uuid_get( async fn uuid_get(
@ -291,7 +301,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UuidGetResponse, ApiError> context: &C) -> Result<UuidGetResponse, ApiError>
{ {
info!("uuid_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("uuid_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn xml_extra_post( async fn xml_extra_post(
@ -300,7 +310,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<XmlExtraPostResponse, ApiError> context: &C) -> Result<XmlExtraPostResponse, ApiError>
{ {
info!("xml_extra_post({:?}) - X-Span-ID: {:?}", duplicate_xml_object, context.get().0.clone()); info!("xml_extra_post({:?}) - X-Span-ID: {:?}", duplicate_xml_object, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn xml_other_post( async fn xml_other_post(
@ -309,7 +319,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<XmlOtherPostResponse, ApiError> context: &C) -> Result<XmlOtherPostResponse, ApiError>
{ {
info!("xml_other_post({:?}) - X-Span-ID: {:?}", another_xml_object, context.get().0.clone()); info!("xml_other_post({:?}) - X-Span-ID: {:?}", another_xml_object, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn xml_other_put( async fn xml_other_put(
@ -318,7 +328,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<XmlOtherPutResponse, ApiError> context: &C) -> Result<XmlOtherPutResponse, ApiError>
{ {
info!("xml_other_put({:?}) - X-Span-ID: {:?}", another_xml_array, context.get().0.clone()); info!("xml_other_put({:?}) - X-Span-ID: {:?}", another_xml_array, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Post an array /// Post an array
@ -328,7 +338,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<XmlPostResponse, ApiError> context: &C) -> Result<XmlPostResponse, ApiError>
{ {
info!("xml_post({:?}) - X-Span-ID: {:?}", xml_array, context.get().0.clone()); info!("xml_post({:?}) - X-Span-ID: {:?}", xml_array, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn xml_put( async fn xml_put(
@ -337,7 +347,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<XmlPutResponse, ApiError> context: &C) -> Result<XmlPutResponse, ApiError>
{ {
info!("xml_put({:?}) - X-Span-ID: {:?}", xml_object, context.get().0.clone()); info!("xml_put({:?}) - X-Span-ID: {:?}", xml_object, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn create_repo( async fn create_repo(
@ -346,7 +356,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CreateRepoResponse, ApiError> context: &C) -> Result<CreateRepoResponse, ApiError>
{ {
info!("create_repo({:?}) - X-Span-ID: {:?}", object_param, context.get().0.clone()); info!("create_repo({:?}) - X-Span-ID: {:?}", object_param, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn get_repo_info( async fn get_repo_info(
@ -355,7 +365,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetRepoInfoResponse, ApiError> context: &C) -> Result<GetRepoInfoResponse, ApiError>
{ {
info!("get_repo_info(\"{}\") - X-Span-ID: {:?}", repo_id, context.get().0.clone()); info!("get_repo_info(\"{}\") - X-Span-ID: {:?}", repo_id, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
} }

View File

@ -0,0 +1,129 @@
use swagger::{
ApiError,
auth::{Basic, Bearer},
Has,
XSpanIdString};
use openapi_v3::{AuthenticationApi, Claims};
use crate::server::Server;
use jsonwebtoken::{decode, errors as JwtError, decode_header, DecodingKey, TokenData, Validation};
use swagger::auth::Authorization;
use log::{error, debug};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
/// Get a dummy claim with full permissions (all scopes) for testing purposes
fn full_permission_claim() -> Claims {
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "mini-bank-IDP".to_owned(),
aud: "org.acme.Resource_Server".to_string(),
// added a very long expiry time
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
"test.read",
"test.write",
].join(", ")
}
}
/// Extract the data from a Bearer token using the provided Key (secret) and using the HS512-algorithm in this example.
fn extract_token_data(token: &str, key: &[u8]) -> Result<TokenData<Claims>, JwtError::Error> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header = decode_header(token)?;
let validation = {
let mut validation = Validation::new(header.alg);
validation.set_audience(&["org.acme.Resource_Server"]);
validation.validate_exp = true;
validation
};
let token_data = decode::<Claims>(
&token,
&DecodingKey::from_secret(key),
&validation,
)?;
Ok(token_data)
}
/// Build a swagger-Authorization based on the claims (Assuming claims have been extracted from a validated token)
fn build_authorization(claims: Claims) -> Authorization {
let mut scopes = std::collections::BTreeSet::<String>::new();
claims
.scopes
.split(",")
.map(|s| s.trim())
.for_each(|s| {let _ = scopes.insert(s.to_string()); });
let scopes = swagger::auth::Scopes::Some(scopes);
Authorization{
subject: claims.sub,
scopes,
issuer: Some(claims.iss)}
}
fn get_jwt_error_string(error: JwtError::Error) -> String {
match error.kind() {
JwtError::ErrorKind::InvalidSignature => "Incorrect token signature".to_owned(),
JwtError::ErrorKind::InvalidAlgorithm => "The Algorithm is not correct".to_owned(),
JwtError::ErrorKind::ExpiredSignature => "The token has expired".to_owned(),
JwtError::ErrorKind::Base64(e) => format!("Base64 decode failed: {e}"),
JwtError::ErrorKind::Json(e) => format!("JSON decoding: {e}"),
JwtError::ErrorKind::Utf8(e) => format!("Invalid UTF-8: {e}"),
_ => error.to_string()
}
}
impl<C> AuthenticationApi for Server<C> where C: Has<XSpanIdString> + Send + Sync {
/// Implementation of the method to map a Bearer-token to an Authorization
fn bearer_authorization(&self, bearer: &Bearer) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Bearer-token, {bearer:#?}");
match extract_token_data(&bearer.token, b"secret") {
Ok(auth_data) => {
debug!("\tUnpack auth_data as: {auth_data:#?}");
let authorization = build_authorization(auth_data.claims);
Ok(authorization)
},
Err(err) => {
let msg = get_jwt_error_string(err);
error!("Failed to unpack Bearer-token: {msg}");
Err(ApiError(msg))
}
}
}
/// Implementation of the method to map an api-key to an Authorization
fn apikey_authorization(&self, api_key: &str) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received api-key, {api_key:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
/// Implementation of the method to map a basic authentication (username and password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Basic-token, {basic:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
}

View File

@ -0,0 +1,62 @@
use std::collections::BTreeSet;
use crate::server::Authorization;
use serde::{Deserialize, Serialize};
use swagger::{ApiError, auth::{Basic, Bearer}};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub iss: String,
pub aud: String,
pub company: String,
pub exp: u64,
pub scopes: String,
}
pub trait AuthenticationApi {
/// Method should be implemented (see example-code) to map Bearer-token to an Authorization
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map ApiKey to an Authorization
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map Basic (Username:password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError>;
}
// Implement it for AllowAllAuthenticator (dummy is needed, but should not used as we have Bearer authorization)
use swagger::auth::{AllowAllAuthenticator, RcBound, Scopes};
fn dummy_authorization() -> Authorization {
// Is called when MakeAllowAllAuthenticator is added to the stack. This is not needed as we have Bearer-authorization in the example-code.
// However, if you want to use it anyway this can not be unimplemented, so dummy implementation added.
// unimplemented!()
Authorization{
subject: "Dummmy".to_owned(),
scopes: Scopes::Some(BTreeSet::new()), // create an empty scope, as this should not be used
issuer: None
}
}
impl<T, RC> AuthenticationApi for AllowAllAuthenticator<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static {
/// Get method to map Bearer-token to an Authorization
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map api-key to an Authorization
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map basic token to an Authorization
fn basic_authorization(&self, _basic: &Basic) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
}

View File

@ -14,8 +14,7 @@ use swagger::auth::Scopes;
use url::form_urlencoded; use url::form_urlencoded;
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::models; use crate::{models, header, AuthenticationApi};
use crate::header;
pub use crate::context; pub use crate::context;
@ -52,6 +51,7 @@ mod paths {
} }
pub struct MakeService<T, C> where pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
@ -72,6 +72,7 @@ impl<T, C> MakeService<T, C> where
} }
} }
impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static

View File

@ -8,7 +8,8 @@ use std::marker::PhantomData;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::auth::{AuthData, Authorization, Bearer, Scopes}; use swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString}; use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api; use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> { pub struct MakeAddContext<T, A> {
inner: T, inner: T,
@ -89,7 +90,7 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
B: Push<Option<AuthData>, Result=C>, B: Push<Option<AuthData>, Result=C>,
C: Push<Option<Authorization>, Result=D>, C: Push<Option<Authorization>, Result=D>,
D: Send + 'static, D: Send + 'static,
T: Service<(Request<ReqBody>, D)> T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{ {
type Error = T::Error; type Error = T::Error;
type Future = T::Future; type Future = T::Future;
@ -108,9 +109,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
use swagger::auth::Bearer; use swagger::auth::Bearer;
use std::ops::Deref; use std::ops::Deref;
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) { if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
let authorization = self.inner.bearer_authorization(&bearer);
let auth_data = AuthData::Bearer(bearer); let auth_data = AuthData::Bearer(bearer);
let context = context.push(Some(auth_data)); let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>); let context = match authorization {
Ok(auth) => context.push(Some(auth)),
Err(err) => {
error!("Error during Authorization: {err:?}");
context.push(None::<Authorization>)
}
};
return self.inner.call((request, context)) return self.inner.call((request, context))
} }

View File

@ -1,19 +1,26 @@
#![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)] #![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)]
#![allow(unused_imports, unused_attributes)] #![allow(unused_imports, unused_attributes)]
#![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names, clippy::too_many_arguments)] #![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names)]
use async_trait::async_trait; use async_trait::async_trait;
use futures::Stream; use futures::Stream;
use std::error::Error; use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper}; use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>; type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = ""; pub const BASE_PATH: &str = "";
pub const API_VERSION: &str = "1.0.7"; pub const API_VERSION: &str = "1.0.7";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
#[must_use] #[must_use]
pub enum AnyOfGetResponse { pub enum AnyOfGetResponse {

View File

@ -14,8 +14,7 @@ use swagger::auth::Scopes;
use url::form_urlencoded; use url::form_urlencoded;
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::models; use crate::{models, header, AuthenticationApi};
use crate::header;
pub use crate::context; pub use crate::context;
@ -50,6 +49,8 @@ use crate::{Api,
GetRepoInfoResponse GetRepoInfoResponse
}; };
mod server_auth;
pub mod callbacks; pub mod callbacks;
mod paths { mod paths {
@ -122,6 +123,7 @@ mod paths {
pub(crate) static ID_XML_OTHER: usize = 23; pub(crate) static ID_XML_OTHER: usize = 23;
} }
pub struct MakeService<T, C> where pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
@ -142,6 +144,7 @@ impl<T, C> MakeService<T, C> where
} }
} }
impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
@ -1269,11 +1272,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_object_untyped_props: Option<models::ObjectUntypedProps> = if !body.is_empty() { let param_object_untyped_props: Option<models::ObjectUntypedProps> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_object_untyped_props) => param_object_untyped_props, Ok(param_object_untyped_props) => param_object_untyped_props,
Err(_) => None, Err(_) => None,
} }
@ -1369,11 +1371,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_duplicate_xml_object: Option<models::DuplicateXmlObject> = if !body.is_empty() { let param_duplicate_xml_object: Option<models::DuplicateXmlObject> = if !body.is_empty() {
let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body); let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_duplicate_xml_object) => param_duplicate_xml_object, Ok(param_duplicate_xml_object) => param_duplicate_xml_object,
Err(_) => None, Err(_) => None,
} }
@ -1437,11 +1438,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_another_xml_object: Option<models::AnotherXmlObject> = if !body.is_empty() { let param_another_xml_object: Option<models::AnotherXmlObject> = if !body.is_empty() {
let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body); let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_another_xml_object) => param_another_xml_object, Ok(param_another_xml_object) => param_another_xml_object,
Err(_) => None, Err(_) => None,
} }
@ -1516,11 +1516,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_another_xml_array: Option<models::AnotherXmlArray> = if !body.is_empty() { let param_another_xml_array: Option<models::AnotherXmlArray> = if !body.is_empty() {
let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body); let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_another_xml_array) => param_another_xml_array, Ok(param_another_xml_array) => param_another_xml_array,
Err(_) => None, Err(_) => None,
} }
@ -1584,11 +1583,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_xml_array: Option<models::XmlArray> = if !body.is_empty() { let param_xml_array: Option<models::XmlArray> = if !body.is_empty() {
let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body); let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_xml_array) => param_xml_array, Ok(param_xml_array) => param_xml_array,
Err(_) => None, Err(_) => None,
} }
@ -1652,11 +1650,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_xml_object: Option<models::XmlObject> = if !body.is_empty() { let param_xml_object: Option<models::XmlObject> = if !body.is_empty() {
let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body); let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_xml_object) => param_xml_object, Ok(param_xml_object) => param_xml_object,
Err(_) => None, Err(_) => None,
} }
@ -1720,11 +1717,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_object_param: Option<models::ObjectParam> = if !body.is_empty() { let param_object_param: Option<models::ObjectParam> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_object_param) => param_object_param, Ok(param_object_param) => param_object_param,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)

View File

@ -0,0 +1,28 @@
use super::Service;
use crate::{Api, AuthenticationApi};
use swagger::{
ApiError,
Authorization,
auth::{Basic, Bearer},
Has,
XSpanIdString};
impl<T,C> AuthenticationApi for Service<T, C> where
T: Api<C> + Clone + Send + 'static + AuthenticationApi,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static {
/// Passthrough of the task to the api-implementation
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError> {
self.api_impl.bearer_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError> {
self.api_impl.apikey_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
self.api_impl.basic_authorization(basic)
}
}

View File

@ -5,14 +5,18 @@ README.md
api/openapi.yaml api/openapi.yaml
docs/default_api.md docs/default_api.md
examples/ca.pem examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs examples/client/main.rs
examples/server-chain.pem examples/server-chain.pem
examples/server-key.pem examples/server-key.pem
examples/server/main.rs examples/server/main.rs
examples/server/server.rs examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs src/client/mod.rs
src/context.rs src/context.rs
src/header.rs src/header.rs
src/lib.rs src/lib.rs
src/models.rs src/models.rs
src/server/mod.rs src/server/mod.rs
src/server/server_auth.rs

View File

@ -5,7 +5,7 @@ authors = ["OpenAPI Generator team and contributors"]
description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)" description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)"
# Override this license by providing a License Object in the OpenAPI. # Override this license by providing a License Object in the OpenAPI.
license = "Unlicense" license = "Unlicense"
edition = "2021" edition = "2018"
[features] [features]
default = ["client", "server"] default = ["client", "server"]
@ -59,6 +59,9 @@ frunk_core = { version = "0.3.0", optional = true }
frunk-enum-derive = { version = "0.2.0", optional = true } frunk-enum-derive = { version = "0.2.0", optional = true }
frunk-enum-core = { version = "0.2.0", optional = true } frunk-enum-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies] [dev-dependencies]
clap = "2.25" clap = "2.25"
env_logger = "0.7" env_logger = "0.7"

View File

@ -0,0 +1,17 @@
use ops_v3::Claims;
use jsonwebtoken::{encode, errors::Error as JwtError, Algorithm, EncodingKey, Header};
use log::debug;
/// build an encrypted token with the provided claims.
pub fn build_token(my_claims: Claims, key: &[u8]) -> Result<String, JwtError> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header =
Header { kid: Some("signing_key".to_owned()), alg: Algorithm::HS512, ..Default::default() };
let token = encode(&header, &my_claims, &EncodingKey::from_secret(key))?;
debug!("Derived token: {:?}", token);
Ok(token)
}

View File

@ -4,7 +4,7 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use futures::{future, Stream, stream}; use futures::{future, Stream, stream};
#[allow(unused_imports)] #[allow(unused_imports)]
use ops_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models, use ops_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
Op10GetResponse, Op10GetResponse,
Op11GetResponse, Op11GetResponse,
Op12GetResponse, Op12GetResponse,
@ -45,6 +45,9 @@ use ops_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models,
}; };
use clap::{App, Arg}; use clap::{App, Arg};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
#[allow(unused_imports)] #[allow(unused_imports)]
use log::info; use log::info;
@ -54,6 +57,10 @@ use swagger::{AuthData, ContextBuilder, EmptyContext, Has, Push, XSpanIdString};
type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString); type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples // rt may be unused if there are no examples
#[allow(unused_mut)] #[allow(unused_mut)]
fn main() { fn main() {
@ -118,6 +125,32 @@ fn main() {
.help("Port to contact")) .help("Port to contact"))
.get_matches(); .get_matches();
// Create Bearer-token with a fixed key (secret) for test purposes.
// In a real (production) system this Bearer token should be obtained via an external Identity/Authentication-server
// Ensure that you set the correct algorithm and encodingkey that matches what is used on the server side.
// See https://github.com/Keats/jsonwebtoken for more information
let auth_token = build_token(
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "my_identity_provider".to_owned(),
// added a very long expiry time
aud: "org.acme.Resource_Server".to_string(),
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
].join(", ")
},
b"secret").unwrap();
let auth_data = if !auth_token.is_empty() {
Some(AuthData::Bearer(swagger::auth::Bearer { token: auth_token}))
} else {
// No Bearer-token available, so return None
None
};
let is_https = matches.is_present("https"); let is_https = matches.is_present("https");
let base_url = format!("{}://{}:{}", let base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" }, if is_https { "https" } else { "http" },
@ -125,7 +158,7 @@ fn main() {
matches.value_of("port").unwrap()); matches.value_of("port").unwrap());
let context: ClientContext = let context: ClientContext =
swagger::make_context!(ContextBuilder, EmptyContext, None as Option<AuthData>, XSpanIdString::default()); swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default());
let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") { let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") {
// Using Simple HTTPS // Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! Main binary entry point for ops_v3 implementation. //! Main binary entry point for ops_v3 implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)] #![allow(missing_docs)]
use clap::{App, Arg}; use clap::{App, Arg};
mod server; mod server;
mod server_auth;
/// Create custom server, wire it to the autogenerated router, /// Create custom server, wire it to the autogenerated router,

View File

@ -30,7 +30,9 @@ pub async fn create(addr: &str, https: bool) {
let service = MakeService::new(server); let service = MakeService::new(server);
let service = MakeAllowAllAuthenticator::new(service, "cosmo"); // This pushes a fourth layer of the middleware-stack even though Swagger assumes only three levels.
// This fourth layer creates an accept-all policy, hower the example-code already acchieves the same via a Bearer-token with full permissions, so next line is not needed (anymore).
// let service = MakeAllowAllAuthenticator::new(service, "cosmo");
#[allow(unused_mut)] #[allow(unused_mut)]
let mut service = let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build(); let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap(); let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop { loop {
if let Ok((tcp, _)) = tcp_listener.accept().await { if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap(); let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
} }
} }
} else { } else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP // Using HTTP
hyper::server::Server::bind(&addr).serve(service).await.unwrap() hyper::server::Server::bind(&addr).serve(service).await.unwrap()
} }
@ -92,6 +96,12 @@ impl<C> Server<C> {
} }
use jsonwebtoken::{decode, encode, errors::Error as JwtError, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize};
use swagger::auth::Authorization;
use crate::server_auth;
use ops_v3::{ use ops_v3::{
Api, Api,
Op10GetResponse, Op10GetResponse,
@ -144,7 +154,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op10GetResponse, ApiError> context: &C) -> Result<Op10GetResponse, ApiError>
{ {
info!("op10_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op10_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op11_get( async fn op11_get(
@ -152,7 +162,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op11GetResponse, ApiError> context: &C) -> Result<Op11GetResponse, ApiError>
{ {
info!("op11_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op11_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op12_get( async fn op12_get(
@ -160,7 +170,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op12GetResponse, ApiError> context: &C) -> Result<Op12GetResponse, ApiError>
{ {
info!("op12_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op12_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op13_get( async fn op13_get(
@ -168,7 +178,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op13GetResponse, ApiError> context: &C) -> Result<Op13GetResponse, ApiError>
{ {
info!("op13_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op13_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op14_get( async fn op14_get(
@ -176,7 +186,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op14GetResponse, ApiError> context: &C) -> Result<Op14GetResponse, ApiError>
{ {
info!("op14_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op14_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op15_get( async fn op15_get(
@ -184,7 +194,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op15GetResponse, ApiError> context: &C) -> Result<Op15GetResponse, ApiError>
{ {
info!("op15_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op15_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op16_get( async fn op16_get(
@ -192,7 +202,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op16GetResponse, ApiError> context: &C) -> Result<Op16GetResponse, ApiError>
{ {
info!("op16_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op16_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op17_get( async fn op17_get(
@ -200,7 +210,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op17GetResponse, ApiError> context: &C) -> Result<Op17GetResponse, ApiError>
{ {
info!("op17_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op17_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op18_get( async fn op18_get(
@ -208,7 +218,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op18GetResponse, ApiError> context: &C) -> Result<Op18GetResponse, ApiError>
{ {
info!("op18_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op18_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op19_get( async fn op19_get(
@ -216,7 +226,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op19GetResponse, ApiError> context: &C) -> Result<Op19GetResponse, ApiError>
{ {
info!("op19_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op19_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op1_get( async fn op1_get(
@ -224,7 +234,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op1GetResponse, ApiError> context: &C) -> Result<Op1GetResponse, ApiError>
{ {
info!("op1_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op1_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op20_get( async fn op20_get(
@ -232,7 +242,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op20GetResponse, ApiError> context: &C) -> Result<Op20GetResponse, ApiError>
{ {
info!("op20_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op20_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op21_get( async fn op21_get(
@ -240,7 +250,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op21GetResponse, ApiError> context: &C) -> Result<Op21GetResponse, ApiError>
{ {
info!("op21_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op21_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op22_get( async fn op22_get(
@ -248,7 +258,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op22GetResponse, ApiError> context: &C) -> Result<Op22GetResponse, ApiError>
{ {
info!("op22_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op22_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op23_get( async fn op23_get(
@ -256,7 +266,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op23GetResponse, ApiError> context: &C) -> Result<Op23GetResponse, ApiError>
{ {
info!("op23_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op23_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op24_get( async fn op24_get(
@ -264,7 +274,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op24GetResponse, ApiError> context: &C) -> Result<Op24GetResponse, ApiError>
{ {
info!("op24_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op24_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op25_get( async fn op25_get(
@ -272,7 +282,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op25GetResponse, ApiError> context: &C) -> Result<Op25GetResponse, ApiError>
{ {
info!("op25_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op25_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op26_get( async fn op26_get(
@ -280,7 +290,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op26GetResponse, ApiError> context: &C) -> Result<Op26GetResponse, ApiError>
{ {
info!("op26_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op26_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op27_get( async fn op27_get(
@ -288,7 +298,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op27GetResponse, ApiError> context: &C) -> Result<Op27GetResponse, ApiError>
{ {
info!("op27_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op27_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op28_get( async fn op28_get(
@ -296,7 +306,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op28GetResponse, ApiError> context: &C) -> Result<Op28GetResponse, ApiError>
{ {
info!("op28_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op28_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op29_get( async fn op29_get(
@ -304,7 +314,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op29GetResponse, ApiError> context: &C) -> Result<Op29GetResponse, ApiError>
{ {
info!("op29_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op29_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op2_get( async fn op2_get(
@ -312,7 +322,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op2GetResponse, ApiError> context: &C) -> Result<Op2GetResponse, ApiError>
{ {
info!("op2_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op2_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op30_get( async fn op30_get(
@ -320,7 +330,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op30GetResponse, ApiError> context: &C) -> Result<Op30GetResponse, ApiError>
{ {
info!("op30_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op30_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op31_get( async fn op31_get(
@ -328,7 +338,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op31GetResponse, ApiError> context: &C) -> Result<Op31GetResponse, ApiError>
{ {
info!("op31_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op31_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op32_get( async fn op32_get(
@ -336,7 +346,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op32GetResponse, ApiError> context: &C) -> Result<Op32GetResponse, ApiError>
{ {
info!("op32_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op32_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op33_get( async fn op33_get(
@ -344,7 +354,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op33GetResponse, ApiError> context: &C) -> Result<Op33GetResponse, ApiError>
{ {
info!("op33_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op33_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op34_get( async fn op34_get(
@ -352,7 +362,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op34GetResponse, ApiError> context: &C) -> Result<Op34GetResponse, ApiError>
{ {
info!("op34_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op34_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op35_get( async fn op35_get(
@ -360,7 +370,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op35GetResponse, ApiError> context: &C) -> Result<Op35GetResponse, ApiError>
{ {
info!("op35_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op35_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op36_get( async fn op36_get(
@ -368,7 +378,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op36GetResponse, ApiError> context: &C) -> Result<Op36GetResponse, ApiError>
{ {
info!("op36_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op36_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op37_get( async fn op37_get(
@ -376,7 +386,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op37GetResponse, ApiError> context: &C) -> Result<Op37GetResponse, ApiError>
{ {
info!("op37_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op37_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op3_get( async fn op3_get(
@ -384,7 +394,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op3GetResponse, ApiError> context: &C) -> Result<Op3GetResponse, ApiError>
{ {
info!("op3_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op3_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op4_get( async fn op4_get(
@ -392,7 +402,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op4GetResponse, ApiError> context: &C) -> Result<Op4GetResponse, ApiError>
{ {
info!("op4_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op4_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op5_get( async fn op5_get(
@ -400,7 +410,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op5GetResponse, ApiError> context: &C) -> Result<Op5GetResponse, ApiError>
{ {
info!("op5_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op5_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op6_get( async fn op6_get(
@ -408,7 +418,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op6GetResponse, ApiError> context: &C) -> Result<Op6GetResponse, ApiError>
{ {
info!("op6_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op6_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op7_get( async fn op7_get(
@ -416,7 +426,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op7GetResponse, ApiError> context: &C) -> Result<Op7GetResponse, ApiError>
{ {
info!("op7_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op7_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op8_get( async fn op8_get(
@ -424,7 +434,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op8GetResponse, ApiError> context: &C) -> Result<Op8GetResponse, ApiError>
{ {
info!("op8_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op8_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn op9_get( async fn op9_get(
@ -432,7 +442,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op9GetResponse, ApiError> context: &C) -> Result<Op9GetResponse, ApiError>
{ {
info!("op9_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("op9_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
} }

View File

@ -0,0 +1,127 @@
use swagger::{
ApiError,
auth::{Basic, Bearer},
Has,
XSpanIdString};
use ops_v3::{AuthenticationApi, Claims};
use crate::server::Server;
use jsonwebtoken::{decode, errors as JwtError, decode_header, DecodingKey, TokenData, Validation};
use swagger::auth::Authorization;
use log::{error, debug};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
/// Get a dummy claim with full permissions (all scopes) for testing purposes
fn full_permission_claim() -> Claims {
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "mini-bank-IDP".to_owned(),
aud: "org.acme.Resource_Server".to_string(),
// added a very long expiry time
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
].join(", ")
}
}
/// Extract the data from a Bearer token using the provided Key (secret) and using the HS512-algorithm in this example.
fn extract_token_data(token: &str, key: &[u8]) -> Result<TokenData<Claims>, JwtError::Error> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header = decode_header(token)?;
let validation = {
let mut validation = Validation::new(header.alg);
validation.set_audience(&["org.acme.Resource_Server"]);
validation.validate_exp = true;
validation
};
let token_data = decode::<Claims>(
&token,
&DecodingKey::from_secret(key),
&validation,
)?;
Ok(token_data)
}
/// Build a swagger-Authorization based on the claims (Assuming claims have been extracted from a validated token)
fn build_authorization(claims: Claims) -> Authorization {
let mut scopes = std::collections::BTreeSet::<String>::new();
claims
.scopes
.split(",")
.map(|s| s.trim())
.for_each(|s| {let _ = scopes.insert(s.to_string()); });
let scopes = swagger::auth::Scopes::Some(scopes);
Authorization{
subject: claims.sub,
scopes,
issuer: Some(claims.iss)}
}
fn get_jwt_error_string(error: JwtError::Error) -> String {
match error.kind() {
JwtError::ErrorKind::InvalidSignature => "Incorrect token signature".to_owned(),
JwtError::ErrorKind::InvalidAlgorithm => "The Algorithm is not correct".to_owned(),
JwtError::ErrorKind::ExpiredSignature => "The token has expired".to_owned(),
JwtError::ErrorKind::Base64(e) => format!("Base64 decode failed: {e}"),
JwtError::ErrorKind::Json(e) => format!("JSON decoding: {e}"),
JwtError::ErrorKind::Utf8(e) => format!("Invalid UTF-8: {e}"),
_ => error.to_string()
}
}
impl<C> AuthenticationApi for Server<C> where C: Has<XSpanIdString> + Send + Sync {
/// Implementation of the method to map a Bearer-token to an Authorization
fn bearer_authorization(&self, bearer: &Bearer) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Bearer-token, {bearer:#?}");
match extract_token_data(&bearer.token, b"secret") {
Ok(auth_data) => {
debug!("\tUnpack auth_data as: {auth_data:#?}");
let authorization = build_authorization(auth_data.claims);
Ok(authorization)
},
Err(err) => {
let msg = get_jwt_error_string(err);
error!("Failed to unpack Bearer-token: {msg}");
Err(ApiError(msg))
}
}
}
/// Implementation of the method to map an api-key to an Authorization
fn apikey_authorization(&self, api_key: &str) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received api-key, {api_key:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
/// Implementation of the method to map a basic authentication (username and password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Basic-token, {basic:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
}

View File

@ -0,0 +1,62 @@
use std::collections::BTreeSet;
use crate::server::Authorization;
use serde::{Deserialize, Serialize};
use swagger::{ApiError, auth::{Basic, Bearer}};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub iss: String,
pub aud: String,
pub company: String,
pub exp: u64,
pub scopes: String,
}
pub trait AuthenticationApi {
/// Method should be implemented (see example-code) to map Bearer-token to an Authorization
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map ApiKey to an Authorization
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map Basic (Username:password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError>;
}
// Implement it for AllowAllAuthenticator (dummy is needed, but should not used as we have Bearer authorization)
use swagger::auth::{AllowAllAuthenticator, RcBound, Scopes};
fn dummy_authorization() -> Authorization {
// Is called when MakeAllowAllAuthenticator is added to the stack. This is not needed as we have Bearer-authorization in the example-code.
// However, if you want to use it anyway this can not be unimplemented, so dummy implementation added.
// unimplemented!()
Authorization{
subject: "Dummmy".to_owned(),
scopes: Scopes::Some(BTreeSet::new()), // create an empty scope, as this should not be used
issuer: None
}
}
impl<T, RC> AuthenticationApi for AllowAllAuthenticator<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static {
/// Get method to map Bearer-token to an Authorization
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map api-key to an Authorization
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map basic token to an Authorization
fn basic_authorization(&self, _basic: &Basic) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
}

View File

@ -8,7 +8,8 @@ use std::marker::PhantomData;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::auth::{AuthData, Authorization, Bearer, Scopes}; use swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString}; use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api; use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> { pub struct MakeAddContext<T, A> {
inner: T, inner: T,
@ -89,7 +90,7 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
B: Push<Option<AuthData>, Result=C>, B: Push<Option<AuthData>, Result=C>,
C: Push<Option<Authorization>, Result=D>, C: Push<Option<Authorization>, Result=D>,
D: Send + 'static, D: Send + 'static,
T: Service<(Request<ReqBody>, D)> T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{ {
type Error = T::Error; type Error = T::Error;
type Future = T::Future; type Future = T::Future;

View File

@ -1,19 +1,26 @@
#![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)] #![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)]
#![allow(unused_imports, unused_attributes)] #![allow(unused_imports, unused_attributes)]
#![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names, clippy::too_many_arguments)] #![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names)]
use async_trait::async_trait; use async_trait::async_trait;
use futures::Stream; use futures::Stream;
use std::error::Error; use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper}; use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>; type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = ""; pub const BASE_PATH: &str = "";
pub const API_VERSION: &str = "0.0.1"; pub const API_VERSION: &str = "0.0.1";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum Op10GetResponse { pub enum Op10GetResponse {
/// OK /// OK

View File

@ -14,8 +14,7 @@ use swagger::auth::Scopes;
use url::form_urlencoded; use url::form_urlencoded;
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::models; use crate::{models, header, AuthenticationApi};
use crate::header;
pub use crate::context; pub use crate::context;
@ -61,6 +60,8 @@ use crate::{Api,
Op9GetResponse Op9GetResponse
}; };
mod server_auth;
mod paths { mod paths {
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -145,6 +146,7 @@ mod paths {
pub(crate) static ID_OP9: usize = 36; pub(crate) static ID_OP9: usize = 36;
} }
pub struct MakeService<T, C> where pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Send + Sync + 'static C: Has<XSpanIdString> + Send + Sync + 'static
@ -165,6 +167,7 @@ impl<T, C> MakeService<T, C> where
} }
} }
impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Send + Sync + 'static C: Has<XSpanIdString> + Send + Sync + 'static

View File

@ -0,0 +1,28 @@
use super::Service;
use crate::{Api, AuthenticationApi};
use swagger::{
ApiError,
Authorization,
auth::{Basic, Bearer},
Has,
XSpanIdString};
impl<T,C> AuthenticationApi for Service<T, C> where
T: Api<C> + Clone + Send + 'static + AuthenticationApi,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static {
/// Passthrough of the task to the api-implementation
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError> {
self.api_impl.bearer_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError> {
self.api_impl.apikey_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
self.api_impl.basic_authorization(basic)
}
}

View File

@ -48,14 +48,18 @@ docs/pet_api.md
docs/store_api.md docs/store_api.md
docs/user_api.md docs/user_api.md
examples/ca.pem examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs examples/client/main.rs
examples/server-chain.pem examples/server-chain.pem
examples/server-key.pem examples/server-key.pem
examples/server/main.rs examples/server/main.rs
examples/server/server.rs examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs src/client/mod.rs
src/context.rs src/context.rs
src/header.rs src/header.rs
src/lib.rs src/lib.rs
src/models.rs src/models.rs
src/server/mod.rs src/server/mod.rs
src/server/server_auth.rs

View File

@ -4,7 +4,7 @@ version = "1.0.0"
authors = ["OpenAPI Generator team and contributors"] authors = ["OpenAPI Generator team and contributors"]
description = "This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\" description = "This spec is mainly for testing Petstore server and contains fake endpoints, models. Please do not use this for any other purpose. Special characters: \" \\"
license = "Apache-2.0" license = "Apache-2.0"
edition = "2021" edition = "2018"
publish = ["crates-io"] publish = ["crates-io"]
[features] [features]
@ -71,6 +71,9 @@ frunk_core = { version = "0.3.0", optional = true }
frunk-enum-derive = { version = "0.2.0", optional = true } frunk-enum-derive = { version = "0.2.0", optional = true }
frunk-enum-core = { version = "0.2.0", optional = true } frunk-enum-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies] [dev-dependencies]
clap = "2.25" clap = "2.25"
env_logger = "0.7" env_logger = "0.7"

View File

@ -0,0 +1,17 @@
use petstore_with_fake_endpoints_models_for_testing::Claims;
use jsonwebtoken::{encode, errors::Error as JwtError, Algorithm, EncodingKey, Header};
use log::debug;
/// build an encrypted token with the provided claims.
pub fn build_token(my_claims: Claims, key: &[u8]) -> Result<String, JwtError> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header =
Header { kid: Some("signing_key".to_owned()), alg: Algorithm::HS512, ..Default::default() };
let token = encode(&header, &my_claims, &EncodingKey::from_secret(key))?;
debug!("Derived token: {:?}", token);
Ok(token)
}

View File

@ -4,7 +4,7 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use futures::{future, Stream, stream}; use futures::{future, Stream, stream};
#[allow(unused_imports)] #[allow(unused_imports)]
use petstore_with_fake_endpoints_models_for_testing::{Api, ApiNoContext, Client, ContextWrapperExt, models, use petstore_with_fake_endpoints_models_for_testing::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
TestSpecialTagsResponse, TestSpecialTagsResponse,
Call123exampleResponse, Call123exampleResponse,
FakeOuterBooleanSerializeResponse, FakeOuterBooleanSerializeResponse,
@ -43,6 +43,9 @@ use petstore_with_fake_endpoints_models_for_testing::{Api, ApiNoContext, Client,
}; };
use clap::{App, Arg}; use clap::{App, Arg};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
#[allow(unused_imports)] #[allow(unused_imports)]
use log::info; use log::info;
@ -52,6 +55,10 @@ use swagger::{AuthData, ContextBuilder, EmptyContext, Has, Push, XSpanIdString};
type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString); type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples // rt may be unused if there are no examples
#[allow(unused_mut)] #[allow(unused_mut)]
fn main() { fn main() {
@ -104,6 +111,34 @@ fn main() {
.help("Port to contact")) .help("Port to contact"))
.get_matches(); .get_matches();
// Create Bearer-token with a fixed key (secret) for test purposes.
// In a real (production) system this Bearer token should be obtained via an external Identity/Authentication-server
// Ensure that you set the correct algorithm and encodingkey that matches what is used on the server side.
// See https://github.com/Keats/jsonwebtoken for more information
let auth_token = build_token(
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "my_identity_provider".to_owned(),
// added a very long expiry time
aud: "org.acme.Resource_Server".to_string(),
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
"write:pets",
"read:pets",
].join(", ")
},
b"secret").unwrap();
let auth_data = if !auth_token.is_empty() {
Some(AuthData::Bearer(swagger::auth::Bearer { token: auth_token}))
} else {
// No Bearer-token available, so return None
None
};
let is_https = matches.is_present("https"); let is_https = matches.is_present("https");
let base_url = format!("{}://{}:{}", let base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" }, if is_https { "https" } else { "http" },
@ -111,7 +146,7 @@ fn main() {
matches.value_of("port").unwrap()); matches.value_of("port").unwrap());
let context: ClientContext = let context: ClientContext =
swagger::make_context!(ContextBuilder, EmptyContext, None as Option<AuthData>, XSpanIdString::default()); swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default());
let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") { let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") {
// Using Simple HTTPS // Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! Main binary entry point for petstore_with_fake_endpoints_models_for_testing implementation. //! Main binary entry point for petstore_with_fake_endpoints_models_for_testing implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)] #![allow(missing_docs)]
use clap::{App, Arg}; use clap::{App, Arg};
mod server; mod server;
mod server_auth;
/// Create custom server, wire it to the autogenerated router, /// Create custom server, wire it to the autogenerated router,

View File

@ -30,7 +30,9 @@ pub async fn create(addr: &str, https: bool) {
let service = MakeService::new(server); let service = MakeService::new(server);
let service = MakeAllowAllAuthenticator::new(service, "cosmo"); // This pushes a fourth layer of the middleware-stack even though Swagger assumes only three levels.
// This fourth layer creates an accept-all policy, hower the example-code already acchieves the same via a Bearer-token with full permissions, so next line is not needed (anymore).
// let service = MakeAllowAllAuthenticator::new(service, "cosmo");
#[allow(unused_mut)] #[allow(unused_mut)]
let mut service = let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build(); let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap(); let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop { loop {
if let Ok((tcp, _)) = tcp_listener.accept().await { if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap(); let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
} }
} }
} else { } else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP // Using HTTP
hyper::server::Server::bind(&addr).serve(service).await.unwrap() hyper::server::Server::bind(&addr).serve(service).await.unwrap()
} }
@ -92,6 +96,12 @@ impl<C> Server<C> {
} }
use jsonwebtoken::{decode, encode, errors::Error as JwtError, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize};
use swagger::auth::Authorization;
use crate::server_auth;
use petstore_with_fake_endpoints_models_for_testing::{ use petstore_with_fake_endpoints_models_for_testing::{
Api, Api,
TestSpecialTagsResponse, TestSpecialTagsResponse,
@ -144,7 +154,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestSpecialTagsResponse, ApiError> context: &C) -> Result<TestSpecialTagsResponse, ApiError>
{ {
info!("test_special_tags({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("test_special_tags({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn call123example( async fn call123example(
@ -152,7 +162,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Call123exampleResponse, ApiError> context: &C) -> Result<Call123exampleResponse, ApiError>
{ {
info!("call123example() - X-Span-ID: {:?}", context.get().0.clone()); info!("call123example() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn fake_outer_boolean_serialize( async fn fake_outer_boolean_serialize(
@ -161,7 +171,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FakeOuterBooleanSerializeResponse, ApiError> context: &C) -> Result<FakeOuterBooleanSerializeResponse, ApiError>
{ {
info!("fake_outer_boolean_serialize({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("fake_outer_boolean_serialize({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn fake_outer_composite_serialize( async fn fake_outer_composite_serialize(
@ -170,7 +180,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FakeOuterCompositeSerializeResponse, ApiError> context: &C) -> Result<FakeOuterCompositeSerializeResponse, ApiError>
{ {
info!("fake_outer_composite_serialize({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("fake_outer_composite_serialize({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn fake_outer_number_serialize( async fn fake_outer_number_serialize(
@ -179,7 +189,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FakeOuterNumberSerializeResponse, ApiError> context: &C) -> Result<FakeOuterNumberSerializeResponse, ApiError>
{ {
info!("fake_outer_number_serialize({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("fake_outer_number_serialize({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn fake_outer_string_serialize( async fn fake_outer_string_serialize(
@ -188,7 +198,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FakeOuterStringSerializeResponse, ApiError> context: &C) -> Result<FakeOuterStringSerializeResponse, ApiError>
{ {
info!("fake_outer_string_serialize({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("fake_outer_string_serialize({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn fake_response_with_numerical_description( async fn fake_response_with_numerical_description(
@ -196,7 +206,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FakeResponseWithNumericalDescriptionResponse, ApiError> context: &C) -> Result<FakeResponseWithNumericalDescriptionResponse, ApiError>
{ {
info!("fake_response_with_numerical_description() - X-Span-ID: {:?}", context.get().0.clone()); info!("fake_response_with_numerical_description() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn hyphen_param( async fn hyphen_param(
@ -205,7 +215,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<HyphenParamResponse, ApiError> context: &C) -> Result<HyphenParamResponse, ApiError>
{ {
info!("hyphen_param(\"{}\") - X-Span-ID: {:?}", hyphen_param, context.get().0.clone()); info!("hyphen_param(\"{}\") - X-Span-ID: {:?}", hyphen_param, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn test_body_with_query_params( async fn test_body_with_query_params(
@ -215,7 +225,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestBodyWithQueryParamsResponse, ApiError> context: &C) -> Result<TestBodyWithQueryParamsResponse, ApiError>
{ {
info!("test_body_with_query_params(\"{}\", {:?}) - X-Span-ID: {:?}", query, body, context.get().0.clone()); info!("test_body_with_query_params(\"{}\", {:?}) - X-Span-ID: {:?}", query, body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// To test \"client\" model /// To test \"client\" model
@ -225,7 +235,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestClientModelResponse, ApiError> context: &C) -> Result<TestClientModelResponse, ApiError>
{ {
info!("test_client_model({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("test_client_model({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Fake endpoint for testing various parameters 假端點 偽のエンドポイント 가짜 엔드 포인트 /// Fake endpoint for testing various parameters 假端點 偽のエンドポイント 가짜 엔드 포인트
@ -248,7 +258,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestEndpointParametersResponse, ApiError> context: &C) -> Result<TestEndpointParametersResponse, ApiError>
{ {
info!("test_endpoint_parameters({}, {}, \"{}\", {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}) - X-Span-ID: {:?}", number, double, pattern_without_delimiter, byte, integer, int32, int64, float, string, binary, date, date_time, password, callback, context.get().0.clone()); info!("test_endpoint_parameters({}, {}, \"{}\", {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}) - X-Span-ID: {:?}", number, double, pattern_without_delimiter, byte, integer, int32, int64, float, string, binary, date, date_time, password, callback, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// To test enum parameters /// To test enum parameters
@ -264,7 +274,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestEnumParametersResponse, ApiError> context: &C) -> Result<TestEnumParametersResponse, ApiError>
{ {
info!("test_enum_parameters({:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}) - X-Span-ID: {:?}", enum_header_string_array, enum_header_string, enum_query_string_array, enum_query_string, enum_query_integer, enum_query_double, enum_form_string, context.get().0.clone()); info!("test_enum_parameters({:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}) - X-Span-ID: {:?}", enum_header_string_array, enum_header_string, enum_query_string_array, enum_query_string, enum_query_integer, enum_query_double, enum_form_string, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// test inline additionalProperties /// test inline additionalProperties
@ -274,7 +284,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestInlineAdditionalPropertiesResponse, ApiError> context: &C) -> Result<TestInlineAdditionalPropertiesResponse, ApiError>
{ {
info!("test_inline_additional_properties({:?}) - X-Span-ID: {:?}", param, context.get().0.clone()); info!("test_inline_additional_properties({:?}) - X-Span-ID: {:?}", param, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// test json serialization of form data /// test json serialization of form data
@ -285,7 +295,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestJsonFormDataResponse, ApiError> context: &C) -> Result<TestJsonFormDataResponse, ApiError>
{ {
info!("test_json_form_data(\"{}\", \"{}\") - X-Span-ID: {:?}", param, param2, context.get().0.clone()); info!("test_json_form_data(\"{}\", \"{}\") - X-Span-ID: {:?}", param, param2, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// To test class name in snake case /// To test class name in snake case
@ -295,7 +305,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestClassnameResponse, ApiError> context: &C) -> Result<TestClassnameResponse, ApiError>
{ {
info!("test_classname({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("test_classname({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Add a new pet to the store /// Add a new pet to the store
@ -305,7 +315,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<AddPetResponse, ApiError> context: &C) -> Result<AddPetResponse, ApiError>
{ {
info!("add_pet({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("add_pet({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Deletes a pet /// Deletes a pet
@ -316,7 +326,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<DeletePetResponse, ApiError> context: &C) -> Result<DeletePetResponse, ApiError>
{ {
info!("delete_pet({}, {:?}) - X-Span-ID: {:?}", pet_id, api_key, context.get().0.clone()); info!("delete_pet({}, {:?}) - X-Span-ID: {:?}", pet_id, api_key, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Finds Pets by status /// Finds Pets by status
@ -326,7 +336,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FindPetsByStatusResponse, ApiError> context: &C) -> Result<FindPetsByStatusResponse, ApiError>
{ {
info!("find_pets_by_status({:?}) - X-Span-ID: {:?}", status, context.get().0.clone()); info!("find_pets_by_status({:?}) - X-Span-ID: {:?}", status, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Finds Pets by tags /// Finds Pets by tags
@ -336,7 +346,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FindPetsByTagsResponse, ApiError> context: &C) -> Result<FindPetsByTagsResponse, ApiError>
{ {
info!("find_pets_by_tags({:?}) - X-Span-ID: {:?}", tags, context.get().0.clone()); info!("find_pets_by_tags({:?}) - X-Span-ID: {:?}", tags, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Find pet by ID /// Find pet by ID
@ -346,7 +356,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetPetByIdResponse, ApiError> context: &C) -> Result<GetPetByIdResponse, ApiError>
{ {
info!("get_pet_by_id({}) - X-Span-ID: {:?}", pet_id, context.get().0.clone()); info!("get_pet_by_id({}) - X-Span-ID: {:?}", pet_id, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Update an existing pet /// Update an existing pet
@ -356,7 +366,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UpdatePetResponse, ApiError> context: &C) -> Result<UpdatePetResponse, ApiError>
{ {
info!("update_pet({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("update_pet({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Updates a pet in the store with form data /// Updates a pet in the store with form data
@ -368,7 +378,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UpdatePetWithFormResponse, ApiError> context: &C) -> Result<UpdatePetWithFormResponse, ApiError>
{ {
info!("update_pet_with_form({}, {:?}, {:?}) - X-Span-ID: {:?}", pet_id, name, status, context.get().0.clone()); info!("update_pet_with_form({}, {:?}, {:?}) - X-Span-ID: {:?}", pet_id, name, status, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// uploads an image /// uploads an image
@ -380,7 +390,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UploadFileResponse, ApiError> context: &C) -> Result<UploadFileResponse, ApiError>
{ {
info!("upload_file({}, {:?}, {:?}) - X-Span-ID: {:?}", pet_id, additional_metadata, file, context.get().0.clone()); info!("upload_file({}, {:?}, {:?}) - X-Span-ID: {:?}", pet_id, additional_metadata, file, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Delete purchase order by ID /// Delete purchase order by ID
@ -390,7 +400,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<DeleteOrderResponse, ApiError> context: &C) -> Result<DeleteOrderResponse, ApiError>
{ {
info!("delete_order(\"{}\") - X-Span-ID: {:?}", order_id, context.get().0.clone()); info!("delete_order(\"{}\") - X-Span-ID: {:?}", order_id, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Returns pet inventories by status /// Returns pet inventories by status
@ -399,7 +409,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetInventoryResponse, ApiError> context: &C) -> Result<GetInventoryResponse, ApiError>
{ {
info!("get_inventory() - X-Span-ID: {:?}", context.get().0.clone()); info!("get_inventory() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Find purchase order by ID /// Find purchase order by ID
@ -409,7 +419,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetOrderByIdResponse, ApiError> context: &C) -> Result<GetOrderByIdResponse, ApiError>
{ {
info!("get_order_by_id({}) - X-Span-ID: {:?}", order_id, context.get().0.clone()); info!("get_order_by_id({}) - X-Span-ID: {:?}", order_id, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Place an order for a pet /// Place an order for a pet
@ -419,7 +429,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<PlaceOrderResponse, ApiError> context: &C) -> Result<PlaceOrderResponse, ApiError>
{ {
info!("place_order({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("place_order({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Create user /// Create user
@ -429,7 +439,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CreateUserResponse, ApiError> context: &C) -> Result<CreateUserResponse, ApiError>
{ {
info!("create_user({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("create_user({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Creates list of users with given input array /// Creates list of users with given input array
@ -439,7 +449,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CreateUsersWithArrayInputResponse, ApiError> context: &C) -> Result<CreateUsersWithArrayInputResponse, ApiError>
{ {
info!("create_users_with_array_input({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("create_users_with_array_input({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Creates list of users with given input array /// Creates list of users with given input array
@ -449,7 +459,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CreateUsersWithListInputResponse, ApiError> context: &C) -> Result<CreateUsersWithListInputResponse, ApiError>
{ {
info!("create_users_with_list_input({:?}) - X-Span-ID: {:?}", body, context.get().0.clone()); info!("create_users_with_list_input({:?}) - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Delete user /// Delete user
@ -459,7 +469,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<DeleteUserResponse, ApiError> context: &C) -> Result<DeleteUserResponse, ApiError>
{ {
info!("delete_user(\"{}\") - X-Span-ID: {:?}", username, context.get().0.clone()); info!("delete_user(\"{}\") - X-Span-ID: {:?}", username, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Get user by user name /// Get user by user name
@ -469,7 +479,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetUserByNameResponse, ApiError> context: &C) -> Result<GetUserByNameResponse, ApiError>
{ {
info!("get_user_by_name(\"{}\") - X-Span-ID: {:?}", username, context.get().0.clone()); info!("get_user_by_name(\"{}\") - X-Span-ID: {:?}", username, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Logs user into the system /// Logs user into the system
@ -480,7 +490,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<LoginUserResponse, ApiError> context: &C) -> Result<LoginUserResponse, ApiError>
{ {
info!("login_user(\"{}\", \"{}\") - X-Span-ID: {:?}", username, password, context.get().0.clone()); info!("login_user(\"{}\", \"{}\") - X-Span-ID: {:?}", username, password, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Logs out current logged in user session /// Logs out current logged in user session
@ -489,7 +499,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<LogoutUserResponse, ApiError> context: &C) -> Result<LogoutUserResponse, ApiError>
{ {
info!("logout_user() - X-Span-ID: {:?}", context.get().0.clone()); info!("logout_user() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Updated user /// Updated user
@ -500,7 +510,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UpdateUserResponse, ApiError> context: &C) -> Result<UpdateUserResponse, ApiError>
{ {
info!("update_user(\"{}\", {:?}) - X-Span-ID: {:?}", username, body, context.get().0.clone()); info!("update_user(\"{}\", {:?}) - X-Span-ID: {:?}", username, body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
} }

View File

@ -0,0 +1,129 @@
use swagger::{
ApiError,
auth::{Basic, Bearer},
Has,
XSpanIdString};
use petstore_with_fake_endpoints_models_for_testing::{AuthenticationApi, Claims};
use crate::server::Server;
use jsonwebtoken::{decode, errors as JwtError, decode_header, DecodingKey, TokenData, Validation};
use swagger::auth::Authorization;
use log::{error, debug};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
/// Get a dummy claim with full permissions (all scopes) for testing purposes
fn full_permission_claim() -> Claims {
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "mini-bank-IDP".to_owned(),
aud: "org.acme.Resource_Server".to_string(),
// added a very long expiry time
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
"write:pets",
"read:pets",
].join(", ")
}
}
/// Extract the data from a Bearer token using the provided Key (secret) and using the HS512-algorithm in this example.
fn extract_token_data(token: &str, key: &[u8]) -> Result<TokenData<Claims>, JwtError::Error> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header = decode_header(token)?;
let validation = {
let mut validation = Validation::new(header.alg);
validation.set_audience(&["org.acme.Resource_Server"]);
validation.validate_exp = true;
validation
};
let token_data = decode::<Claims>(
&token,
&DecodingKey::from_secret(key),
&validation,
)?;
Ok(token_data)
}
/// Build a swagger-Authorization based on the claims (Assuming claims have been extracted from a validated token)
fn build_authorization(claims: Claims) -> Authorization {
let mut scopes = std::collections::BTreeSet::<String>::new();
claims
.scopes
.split(",")
.map(|s| s.trim())
.for_each(|s| {let _ = scopes.insert(s.to_string()); });
let scopes = swagger::auth::Scopes::Some(scopes);
Authorization{
subject: claims.sub,
scopes,
issuer: Some(claims.iss)}
}
fn get_jwt_error_string(error: JwtError::Error) -> String {
match error.kind() {
JwtError::ErrorKind::InvalidSignature => "Incorrect token signature".to_owned(),
JwtError::ErrorKind::InvalidAlgorithm => "The Algorithm is not correct".to_owned(),
JwtError::ErrorKind::ExpiredSignature => "The token has expired".to_owned(),
JwtError::ErrorKind::Base64(e) => format!("Base64 decode failed: {e}"),
JwtError::ErrorKind::Json(e) => format!("JSON decoding: {e}"),
JwtError::ErrorKind::Utf8(e) => format!("Invalid UTF-8: {e}"),
_ => error.to_string()
}
}
impl<C> AuthenticationApi for Server<C> where C: Has<XSpanIdString> + Send + Sync {
/// Implementation of the method to map a Bearer-token to an Authorization
fn bearer_authorization(&self, bearer: &Bearer) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Bearer-token, {bearer:#?}");
match extract_token_data(&bearer.token, b"secret") {
Ok(auth_data) => {
debug!("\tUnpack auth_data as: {auth_data:#?}");
let authorization = build_authorization(auth_data.claims);
Ok(authorization)
},
Err(err) => {
let msg = get_jwt_error_string(err);
error!("Failed to unpack Bearer-token: {msg}");
Err(ApiError(msg))
}
}
}
/// Implementation of the method to map an api-key to an Authorization
fn apikey_authorization(&self, api_key: &str) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received api-key, {api_key:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
/// Implementation of the method to map a basic authentication (username and password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Basic-token, {basic:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
}

View File

@ -0,0 +1,62 @@
use std::collections::BTreeSet;
use crate::server::Authorization;
use serde::{Deserialize, Serialize};
use swagger::{ApiError, auth::{Basic, Bearer}};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub iss: String,
pub aud: String,
pub company: String,
pub exp: u64,
pub scopes: String,
}
pub trait AuthenticationApi {
/// Method should be implemented (see example-code) to map Bearer-token to an Authorization
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map ApiKey to an Authorization
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map Basic (Username:password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError>;
}
// Implement it for AllowAllAuthenticator (dummy is needed, but should not used as we have Bearer authorization)
use swagger::auth::{AllowAllAuthenticator, RcBound, Scopes};
fn dummy_authorization() -> Authorization {
// Is called when MakeAllowAllAuthenticator is added to the stack. This is not needed as we have Bearer-authorization in the example-code.
// However, if you want to use it anyway this can not be unimplemented, so dummy implementation added.
// unimplemented!()
Authorization{
subject: "Dummmy".to_owned(),
scopes: Scopes::Some(BTreeSet::new()), // create an empty scope, as this should not be used
issuer: None
}
}
impl<T, RC> AuthenticationApi for AllowAllAuthenticator<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static {
/// Get method to map Bearer-token to an Authorization
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map api-key to an Authorization
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map basic token to an Authorization
fn basic_authorization(&self, _basic: &Basic) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
}

View File

@ -8,7 +8,8 @@ use std::marker::PhantomData;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::auth::{AuthData, Authorization, Bearer, Scopes}; use swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString}; use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api; use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> { pub struct MakeAddContext<T, A> {
inner: T, inner: T,
@ -89,7 +90,7 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
B: Push<Option<AuthData>, Result=C>, B: Push<Option<AuthData>, Result=C>,
C: Push<Option<Authorization>, Result=D>, C: Push<Option<Authorization>, Result=D>,
D: Send + 'static, D: Send + 'static,
T: Service<(Request<ReqBody>, D)> T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{ {
type Error = T::Error; type Error = T::Error;
type Future = T::Future; type Future = T::Future;
@ -108,9 +109,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
use swagger::auth::Bearer; use swagger::auth::Bearer;
use std::ops::Deref; use std::ops::Deref;
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) { if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
let authorization = self.inner.bearer_authorization(&bearer);
let auth_data = AuthData::Bearer(bearer); let auth_data = AuthData::Bearer(bearer);
let context = context.push(Some(auth_data)); let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>); let context = match authorization {
Ok(auth) => context.push(Some(auth)),
Err(err) => {
error!("Error during Authorization: {err:?}");
context.push(None::<Authorization>)
}
};
return self.inner.call((request, context)) return self.inner.call((request, context))
} }
@ -119,9 +128,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
use swagger::auth::api_key_from_header; use swagger::auth::api_key_from_header;
if let Some(header) = api_key_from_header(headers, "api_key") { if let Some(header) = api_key_from_header(headers, "api_key") {
let authorization = self.inner.apikey_authorization(&header);
let auth_data = AuthData::ApiKey(header); let auth_data = AuthData::ApiKey(header);
let context = context.push(Some(auth_data)); let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>); let context = match authorization {
Ok(auth) => context.push(Some(auth)),
Err(err) => {
error!("Error during Authorization: {err:?}");
context.push(None::<Authorization>)
}
};
return self.inner.call((request, context)) return self.inner.call((request, context))
} }
@ -132,9 +149,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
.map(|e| e.1.clone().into_owned()) .map(|e| e.1.clone().into_owned())
.next(); .next();
if let Some(key) = key { if let Some(key) = key {
let authorization = self.inner.apikey_authorization(&key);
let auth_data = AuthData::ApiKey(key); let auth_data = AuthData::ApiKey(key);
let context = context.push(Some(auth_data)); let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>); let context = match authorization {
Ok(auth) => context.push(Some(auth)),
Err(err) => {
error!("Error during Authorization: {err:?}");
context.push(None::<Authorization>)
}
};
return self.inner.call((request, context)) return self.inner.call((request, context))
} }
@ -143,9 +168,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
use swagger::auth::Basic; use swagger::auth::Basic;
use std::ops::Deref; use std::ops::Deref;
if let Some(basic) = swagger::auth::from_headers::<Basic>(headers) { if let Some(basic) = swagger::auth::from_headers::<Basic>(headers) {
let authorization = self.inner.basic_authorization(&basic);
let auth_data = AuthData::Basic(basic); let auth_data = AuthData::Basic(basic);
let context = context.push(Some(auth_data)); let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>); let context = match authorization {
Ok(auth) => context.push(Some(auth)),
Err(err) => {
error!("Error during Authorization: {err:?}");
context.push(None::<Authorization>)
}
};
return self.inner.call((request, context)) return self.inner.call((request, context))
} }

View File

@ -1,19 +1,26 @@
#![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)] #![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)]
#![allow(unused_imports, unused_attributes)] #![allow(unused_imports, unused_attributes)]
#![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names, clippy::too_many_arguments)] #![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names)]
use async_trait::async_trait; use async_trait::async_trait;
use futures::Stream; use futures::Stream;
use std::error::Error; use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper}; use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>; type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = "/v2"; pub const BASE_PATH: &str = "/v2";
pub const API_VERSION: &str = "1.0.0"; pub const API_VERSION: &str = "1.0.0";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum TestSpecialTagsResponse { pub enum TestSpecialTagsResponse {
/// successful operation /// successful operation

View File

@ -16,8 +16,7 @@ use multipart::server::Multipart;
use multipart::server::save::SaveResult; use multipart::server::save::SaveResult;
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::models; use crate::{models, header, AuthenticationApi};
use crate::header;
pub use crate::context; pub use crate::context;
@ -61,6 +60,8 @@ use crate::{Api,
UpdateUserResponse UpdateUserResponse
}; };
mod server_auth;
mod paths { mod paths {
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -155,6 +156,7 @@ mod paths {
} }
} }
pub struct MakeService<T, C> where pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
@ -175,6 +177,7 @@ impl<T, C> MakeService<T, C> where
} }
} }
impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
@ -268,11 +271,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::Client> = if !body.is_empty() { let param_body: Option<models::Client> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
@ -378,11 +380,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::OuterBoolean> = if !body.is_empty() { let param_body: Option<models::OuterBoolean> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(_) => None, Err(_) => None,
} }
@ -449,11 +450,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::OuterComposite> = if !body.is_empty() { let param_body: Option<models::OuterComposite> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(_) => None, Err(_) => None,
} }
@ -520,11 +520,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::OuterNumber> = if !body.is_empty() { let param_body: Option<models::OuterNumber> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(_) => None, Err(_) => None,
} }
@ -591,11 +590,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::OuterString> = if !body.is_empty() { let param_body: Option<models::OuterString> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(_) => None, Err(_) => None,
} }
@ -771,11 +769,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::User> = if !body.is_empty() { let param_body: Option<models::User> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
@ -846,11 +843,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::Client> = if !body.is_empty() { let param_body: Option<models::Client> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
@ -1143,11 +1139,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_param: Option<std::collections::HashMap<String, String>> = if !body.is_empty() { let param_param: Option<std::collections::HashMap<String, String>> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_param) => param_param, Ok(param_param) => param_param,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
@ -1262,11 +1257,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::Client> = if !body.is_empty() { let param_body: Option<models::Client> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
@ -1373,11 +1367,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::Pet> = if !body.is_empty() { let param_body: Option<models::Pet> = if !body.is_empty() {
let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body); let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
@ -1813,11 +1806,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::Pet> = if !body.is_empty() { let param_body: Option<models::Pet> = if !body.is_empty() {
let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body); let deserializer = &mut serde_xml_rs::de::Deserializer::new_from_reader(&*body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
@ -2329,11 +2321,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::Order> = if !body.is_empty() { let param_body: Option<models::Order> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
@ -2414,11 +2405,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::User> = if !body.is_empty() { let param_body: Option<models::User> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
@ -2488,11 +2478,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<Vec<models::User>> = if !body.is_empty() { let param_body: Option<Vec<models::User>> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
@ -2562,11 +2551,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<Vec<models::User>> = if !body.is_empty() { let param_body: Option<Vec<models::User>> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)
@ -2942,11 +2930,10 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
let mut unused_elements = Vec::new(); let mut unused_elements = Vec::new();
let param_body: Option<models::User> = if !body.is_empty() { let param_body: Option<models::User> = if !body.is_empty() {
let deserializer = &mut serde_json::Deserializer::from_slice(&body); let deserializer = &mut serde_json::Deserializer::from_slice(&body);
let handle_unknown_field = |path: serde_ignored::Path<'_>| { match serde_ignored::deserialize(deserializer, |path| {
warn!("Ignoring unknown field in body: {}", path); warn!("Ignoring unknown field in body: {}", path);
unused_elements.push(path.to_string()); unused_elements.push(path.to_string());
}; }) {
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
Ok(param_body) => param_body, Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder() Err(e) => return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST) .status(StatusCode::BAD_REQUEST)

View File

@ -0,0 +1,28 @@
use super::Service;
use crate::{Api, AuthenticationApi};
use swagger::{
ApiError,
Authorization,
auth::{Basic, Bearer},
Has,
XSpanIdString};
impl<T,C> AuthenticationApi for Service<T, C> where
T: Api<C> + Clone + Send + 'static + AuthenticationApi,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static {
/// Passthrough of the task to the api-implementation
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError> {
self.api_impl.bearer_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError> {
self.api_impl.apikey_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
self.api_impl.basic_authorization(basic)
}
}

View File

@ -5,14 +5,18 @@ README.md
api/openapi.yaml api/openapi.yaml
docs/default_api.md docs/default_api.md
examples/ca.pem examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs examples/client/main.rs
examples/server-chain.pem examples/server-chain.pem
examples/server-key.pem examples/server-key.pem
examples/server/main.rs examples/server/main.rs
examples/server/server.rs examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs src/client/mod.rs
src/context.rs src/context.rs
src/header.rs src/header.rs
src/lib.rs src/lib.rs
src/models.rs src/models.rs
src/server/mod.rs src/server/mod.rs
src/server/server_auth.rs

View File

@ -5,7 +5,7 @@ authors = ["OpenAPI Generator team and contributors"]
description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)" description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)"
# Override this license by providing a License Object in the OpenAPI. # Override this license by providing a License Object in the OpenAPI.
license = "Unlicense" license = "Unlicense"
edition = "2021" edition = "2018"
[features] [features]
default = ["client", "server"] default = ["client", "server"]
@ -59,6 +59,9 @@ frunk_core = { version = "0.3.0", optional = true }
frunk-enum-derive = { version = "0.2.0", optional = true } frunk-enum-derive = { version = "0.2.0", optional = true }
frunk-enum-core = { version = "0.2.0", optional = true } frunk-enum-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies] [dev-dependencies]
clap = "2.25" clap = "2.25"
env_logger = "0.7" env_logger = "0.7"

View File

@ -0,0 +1,17 @@
use ping_bearer_auth::Claims;
use jsonwebtoken::{encode, errors::Error as JwtError, Algorithm, EncodingKey, Header};
use log::debug;
/// build an encrypted token with the provided claims.
pub fn build_token(my_claims: Claims, key: &[u8]) -> Result<String, JwtError> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header =
Header { kid: Some("signing_key".to_owned()), alg: Algorithm::HS512, ..Default::default() };
let token = encode(&header, &my_claims, &EncodingKey::from_secret(key))?;
debug!("Derived token: {:?}", token);
Ok(token)
}

View File

@ -4,11 +4,14 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use futures::{future, Stream, stream}; use futures::{future, Stream, stream};
#[allow(unused_imports)] #[allow(unused_imports)]
use ping_bearer_auth::{Api, ApiNoContext, Client, ContextWrapperExt, models, use ping_bearer_auth::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
PingGetResponse, PingGetResponse,
}; };
use clap::{App, Arg}; use clap::{App, Arg};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
#[allow(unused_imports)] #[allow(unused_imports)]
use log::info; use log::info;
@ -18,6 +21,10 @@ use swagger::{AuthData, ContextBuilder, EmptyContext, Has, Push, XSpanIdString};
type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString); type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples // rt may be unused if there are no examples
#[allow(unused_mut)] #[allow(unused_mut)]
fn main() { fn main() {
@ -46,6 +53,32 @@ fn main() {
.help("Port to contact")) .help("Port to contact"))
.get_matches(); .get_matches();
// Create Bearer-token with a fixed key (secret) for test purposes.
// In a real (production) system this Bearer token should be obtained via an external Identity/Authentication-server
// Ensure that you set the correct algorithm and encodingkey that matches what is used on the server side.
// See https://github.com/Keats/jsonwebtoken for more information
let auth_token = build_token(
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "my_identity_provider".to_owned(),
// added a very long expiry time
aud: "org.acme.Resource_Server".to_string(),
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
].join(", ")
},
b"secret").unwrap();
let auth_data = if !auth_token.is_empty() {
Some(AuthData::Bearer(swagger::auth::Bearer { token: auth_token}))
} else {
// No Bearer-token available, so return None
None
};
let is_https = matches.is_present("https"); let is_https = matches.is_present("https");
let base_url = format!("{}://{}:{}", let base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" }, if is_https { "https" } else { "http" },
@ -53,7 +86,7 @@ fn main() {
matches.value_of("port").unwrap()); matches.value_of("port").unwrap());
let context: ClientContext = let context: ClientContext =
swagger::make_context!(ContextBuilder, EmptyContext, None as Option<AuthData>, XSpanIdString::default()); swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default());
let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") { let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") {
// Using Simple HTTPS // Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! Main binary entry point for ping_bearer_auth implementation. //! Main binary entry point for ping_bearer_auth implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)] #![allow(missing_docs)]
use clap::{App, Arg}; use clap::{App, Arg};
mod server; mod server;
mod server_auth;
/// Create custom server, wire it to the autogenerated router, /// Create custom server, wire it to the autogenerated router,

View File

@ -30,7 +30,9 @@ pub async fn create(addr: &str, https: bool) {
let service = MakeService::new(server); let service = MakeService::new(server);
let service = MakeAllowAllAuthenticator::new(service, "cosmo"); // This pushes a fourth layer of the middleware-stack even though Swagger assumes only three levels.
// This fourth layer creates an accept-all policy, hower the example-code already acchieves the same via a Bearer-token with full permissions, so next line is not needed (anymore).
// let service = MakeAllowAllAuthenticator::new(service, "cosmo");
#[allow(unused_mut)] #[allow(unused_mut)]
let mut service = let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build(); let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap(); let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop { loop {
if let Ok((tcp, _)) = tcp_listener.accept().await { if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap(); let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
} }
} }
} else { } else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP // Using HTTP
hyper::server::Server::bind(&addr).serve(service).await.unwrap() hyper::server::Server::bind(&addr).serve(service).await.unwrap()
} }
@ -92,6 +96,12 @@ impl<C> Server<C> {
} }
use jsonwebtoken::{decode, encode, errors::Error as JwtError, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize};
use swagger::auth::Authorization;
use crate::server_auth;
use ping_bearer_auth::{ use ping_bearer_auth::{
Api, Api,
PingGetResponse, PingGetResponse,
@ -108,7 +118,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<PingGetResponse, ApiError> context: &C) -> Result<PingGetResponse, ApiError>
{ {
info!("ping_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("ping_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
} }

View File

@ -0,0 +1,127 @@
use swagger::{
ApiError,
auth::{Basic, Bearer},
Has,
XSpanIdString};
use ping_bearer_auth::{AuthenticationApi, Claims};
use crate::server::Server;
use jsonwebtoken::{decode, errors as JwtError, decode_header, DecodingKey, TokenData, Validation};
use swagger::auth::Authorization;
use log::{error, debug};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
/// Get a dummy claim with full permissions (all scopes) for testing purposes
fn full_permission_claim() -> Claims {
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "mini-bank-IDP".to_owned(),
aud: "org.acme.Resource_Server".to_string(),
// added a very long expiry time
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
].join(", ")
}
}
/// Extract the data from a Bearer token using the provided Key (secret) and using the HS512-algorithm in this example.
fn extract_token_data(token: &str, key: &[u8]) -> Result<TokenData<Claims>, JwtError::Error> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header = decode_header(token)?;
let validation = {
let mut validation = Validation::new(header.alg);
validation.set_audience(&["org.acme.Resource_Server"]);
validation.validate_exp = true;
validation
};
let token_data = decode::<Claims>(
&token,
&DecodingKey::from_secret(key),
&validation,
)?;
Ok(token_data)
}
/// Build a swagger-Authorization based on the claims (Assuming claims have been extracted from a validated token)
fn build_authorization(claims: Claims) -> Authorization {
let mut scopes = std::collections::BTreeSet::<String>::new();
claims
.scopes
.split(",")
.map(|s| s.trim())
.for_each(|s| {let _ = scopes.insert(s.to_string()); });
let scopes = swagger::auth::Scopes::Some(scopes);
Authorization{
subject: claims.sub,
scopes,
issuer: Some(claims.iss)}
}
fn get_jwt_error_string(error: JwtError::Error) -> String {
match error.kind() {
JwtError::ErrorKind::InvalidSignature => "Incorrect token signature".to_owned(),
JwtError::ErrorKind::InvalidAlgorithm => "The Algorithm is not correct".to_owned(),
JwtError::ErrorKind::ExpiredSignature => "The token has expired".to_owned(),
JwtError::ErrorKind::Base64(e) => format!("Base64 decode failed: {e}"),
JwtError::ErrorKind::Json(e) => format!("JSON decoding: {e}"),
JwtError::ErrorKind::Utf8(e) => format!("Invalid UTF-8: {e}"),
_ => error.to_string()
}
}
impl<C> AuthenticationApi for Server<C> where C: Has<XSpanIdString> + Send + Sync {
/// Implementation of the method to map a Bearer-token to an Authorization
fn bearer_authorization(&self, bearer: &Bearer) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Bearer-token, {bearer:#?}");
match extract_token_data(&bearer.token, b"secret") {
Ok(auth_data) => {
debug!("\tUnpack auth_data as: {auth_data:#?}");
let authorization = build_authorization(auth_data.claims);
Ok(authorization)
},
Err(err) => {
let msg = get_jwt_error_string(err);
error!("Failed to unpack Bearer-token: {msg}");
Err(ApiError(msg))
}
}
}
/// Implementation of the method to map an api-key to an Authorization
fn apikey_authorization(&self, api_key: &str) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received api-key, {api_key:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
/// Implementation of the method to map a basic authentication (username and password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Basic-token, {basic:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
}

View File

@ -0,0 +1,62 @@
use std::collections::BTreeSet;
use crate::server::Authorization;
use serde::{Deserialize, Serialize};
use swagger::{ApiError, auth::{Basic, Bearer}};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub iss: String,
pub aud: String,
pub company: String,
pub exp: u64,
pub scopes: String,
}
pub trait AuthenticationApi {
/// Method should be implemented (see example-code) to map Bearer-token to an Authorization
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map ApiKey to an Authorization
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map Basic (Username:password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError>;
}
// Implement it for AllowAllAuthenticator (dummy is needed, but should not used as we have Bearer authorization)
use swagger::auth::{AllowAllAuthenticator, RcBound, Scopes};
fn dummy_authorization() -> Authorization {
// Is called when MakeAllowAllAuthenticator is added to the stack. This is not needed as we have Bearer-authorization in the example-code.
// However, if you want to use it anyway this can not be unimplemented, so dummy implementation added.
// unimplemented!()
Authorization{
subject: "Dummmy".to_owned(),
scopes: Scopes::Some(BTreeSet::new()), // create an empty scope, as this should not be used
issuer: None
}
}
impl<T, RC> AuthenticationApi for AllowAllAuthenticator<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static {
/// Get method to map Bearer-token to an Authorization
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map api-key to an Authorization
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map basic token to an Authorization
fn basic_authorization(&self, _basic: &Basic) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
}

View File

@ -8,7 +8,8 @@ use std::marker::PhantomData;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::auth::{AuthData, Authorization, Bearer, Scopes}; use swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString}; use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api; use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> { pub struct MakeAddContext<T, A> {
inner: T, inner: T,
@ -89,7 +90,7 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
B: Push<Option<AuthData>, Result=C>, B: Push<Option<AuthData>, Result=C>,
C: Push<Option<Authorization>, Result=D>, C: Push<Option<Authorization>, Result=D>,
D: Send + 'static, D: Send + 'static,
T: Service<(Request<ReqBody>, D)> T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{ {
type Error = T::Error; type Error = T::Error;
type Future = T::Future; type Future = T::Future;
@ -108,9 +109,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
use swagger::auth::Bearer; use swagger::auth::Bearer;
use std::ops::Deref; use std::ops::Deref;
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) { if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
let authorization = self.inner.bearer_authorization(&bearer);
let auth_data = AuthData::Bearer(bearer); let auth_data = AuthData::Bearer(bearer);
let context = context.push(Some(auth_data)); let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>); let context = match authorization {
Ok(auth) => context.push(Some(auth)),
Err(err) => {
error!("Error during Authorization: {err:?}");
context.push(None::<Authorization>)
}
};
return self.inner.call((request, context)) return self.inner.call((request, context))
} }

View File

@ -1,19 +1,26 @@
#![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)] #![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)]
#![allow(unused_imports, unused_attributes)] #![allow(unused_imports, unused_attributes)]
#![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names, clippy::too_many_arguments)] #![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names)]
use async_trait::async_trait; use async_trait::async_trait;
use futures::Stream; use futures::Stream;
use std::error::Error; use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper}; use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>; type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = ""; pub const BASE_PATH: &str = "";
pub const API_VERSION: &str = "1.0"; pub const API_VERSION: &str = "1.0";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum PingGetResponse { pub enum PingGetResponse {
/// OK /// OK

View File

@ -14,8 +14,7 @@ use swagger::auth::Scopes;
use url::form_urlencoded; use url::form_urlencoded;
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::models; use crate::{models, header, AuthenticationApi};
use crate::header;
pub use crate::context; pub use crate::context;
@ -25,6 +24,8 @@ use crate::{Api,
PingGetResponse PingGetResponse
}; };
mod server_auth;
mod paths { mod paths {
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -37,6 +38,7 @@ mod paths {
pub(crate) static ID_PING: usize = 0; pub(crate) static ID_PING: usize = 0;
} }
pub struct MakeService<T, C> where pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
@ -57,6 +59,7 @@ impl<T, C> MakeService<T, C> where
} }
} }
impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
T: Api<C> + Clone + Send + 'static, T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static

View File

@ -0,0 +1,28 @@
use super::Service;
use crate::{Api, AuthenticationApi};
use swagger::{
ApiError,
Authorization,
auth::{Basic, Bearer},
Has,
XSpanIdString};
impl<T,C> AuthenticationApi for Service<T, C> where
T: Api<C> + Clone + Send + 'static + AuthenticationApi,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static {
/// Passthrough of the task to the api-implementation
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError> {
self.api_impl.bearer_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError> {
self.api_impl.apikey_authorization(token)
}
/// Passthrough of the task to the api-implementation
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
self.api_impl.basic_authorization(basic)
}
}

View File

@ -13,14 +13,18 @@ docs/ObjectOfObjects.md
docs/ObjectOfObjectsInner.md docs/ObjectOfObjectsInner.md
docs/default_api.md docs/default_api.md
examples/ca.pem examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs examples/client/main.rs
examples/server-chain.pem examples/server-chain.pem
examples/server-key.pem examples/server-key.pem
examples/server/main.rs examples/server/main.rs
examples/server/server.rs examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs src/client/mod.rs
src/context.rs src/context.rs
src/header.rs src/header.rs
src/lib.rs src/lib.rs
src/models.rs src/models.rs
src/server/mod.rs src/server/mod.rs
src/server/server_auth.rs

View File

@ -5,7 +5,7 @@ authors = ["OpenAPI Generator team and contributors"]
description = "This spec is for testing rust-server-specific things" description = "This spec is for testing rust-server-specific things"
# Override this license by providing a License Object in the OpenAPI. # Override this license by providing a License Object in the OpenAPI.
license = "Unlicense" license = "Unlicense"
edition = "2021" edition = "2018"
[features] [features]
default = ["client", "server"] default = ["client", "server"]
@ -59,6 +59,9 @@ frunk_core = { version = "0.3.0", optional = true }
frunk-enum-derive = { version = "0.2.0", optional = true } frunk-enum-derive = { version = "0.2.0", optional = true }
frunk-enum-core = { version = "0.2.0", optional = true } frunk-enum-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies] [dev-dependencies]
clap = "2.25" clap = "2.25"
env_logger = "0.7" env_logger = "0.7"

View File

@ -0,0 +1,17 @@
use rust_server_test::Claims;
use jsonwebtoken::{encode, errors::Error as JwtError, Algorithm, EncodingKey, Header};
use log::debug;
/// build an encrypted token with the provided claims.
pub fn build_token(my_claims: Claims, key: &[u8]) -> Result<String, JwtError> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header =
Header { kid: Some("signing_key".to_owned()), alg: Algorithm::HS512, ..Default::default() };
let token = encode(&header, &my_claims, &EncodingKey::from_secret(key))?;
debug!("Derived token: {:?}", token);
Ok(token)
}

View File

@ -4,7 +4,7 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use futures::{future, Stream, stream}; use futures::{future, Stream, stream};
#[allow(unused_imports)] #[allow(unused_imports)]
use rust_server_test::{Api, ApiNoContext, Client, ContextWrapperExt, models, use rust_server_test::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
AllOfGetResponse, AllOfGetResponse,
DummyGetResponse, DummyGetResponse,
DummyPutResponse, DummyPutResponse,
@ -17,6 +17,9 @@ use rust_server_test::{Api, ApiNoContext, Client, ContextWrapperExt, models,
}; };
use clap::{App, Arg}; use clap::{App, Arg};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
#[allow(unused_imports)] #[allow(unused_imports)]
use log::info; use log::info;
@ -26,6 +29,10 @@ use swagger::{AuthData, ContextBuilder, EmptyContext, Has, Push, XSpanIdString};
type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString); type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples // rt may be unused if there are no examples
#[allow(unused_mut)] #[allow(unused_mut)]
fn main() { fn main() {
@ -60,6 +67,32 @@ fn main() {
.help("Port to contact")) .help("Port to contact"))
.get_matches(); .get_matches();
// Create Bearer-token with a fixed key (secret) for test purposes.
// In a real (production) system this Bearer token should be obtained via an external Identity/Authentication-server
// Ensure that you set the correct algorithm and encodingkey that matches what is used on the server side.
// See https://github.com/Keats/jsonwebtoken for more information
let auth_token = build_token(
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "my_identity_provider".to_owned(),
// added a very long expiry time
aud: "org.acme.Resource_Server".to_string(),
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
].join(", ")
},
b"secret").unwrap();
let auth_data = if !auth_token.is_empty() {
Some(AuthData::Bearer(swagger::auth::Bearer { token: auth_token}))
} else {
// No Bearer-token available, so return None
None
};
let is_https = matches.is_present("https"); let is_https = matches.is_present("https");
let base_url = format!("{}://{}:{}", let base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" }, if is_https { "https" } else { "http" },
@ -67,7 +100,7 @@ fn main() {
matches.value_of("port").unwrap()); matches.value_of("port").unwrap());
let context: ClientContext = let context: ClientContext =
swagger::make_context!(ContextBuilder, EmptyContext, None as Option<AuthData>, XSpanIdString::default()); swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default());
let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") { let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") {
// Using Simple HTTPS // Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! Main binary entry point for rust_server_test implementation. //! Main binary entry point for rust_server_test implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)] #![allow(missing_docs)]
use clap::{App, Arg}; use clap::{App, Arg};
mod server; mod server;
mod server_auth;
/// Create custom server, wire it to the autogenerated router, /// Create custom server, wire it to the autogenerated router,

View File

@ -30,7 +30,9 @@ pub async fn create(addr: &str, https: bool) {
let service = MakeService::new(server); let service = MakeService::new(server);
let service = MakeAllowAllAuthenticator::new(service, "cosmo"); // This pushes a fourth layer of the middleware-stack even though Swagger assumes only three levels.
// This fourth layer creates an accept-all policy, hower the example-code already acchieves the same via a Bearer-token with full permissions, so next line is not needed (anymore).
// let service = MakeAllowAllAuthenticator::new(service, "cosmo");
#[allow(unused_mut)] #[allow(unused_mut)]
let mut service = let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build(); let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap(); let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop { loop {
if let Ok((tcp, _)) = tcp_listener.accept().await { if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap(); let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
} }
} }
} else { } else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP // Using HTTP
hyper::server::Server::bind(&addr).serve(service).await.unwrap() hyper::server::Server::bind(&addr).serve(service).await.unwrap()
} }
@ -92,6 +96,12 @@ impl<C> Server<C> {
} }
use jsonwebtoken::{decode, encode, errors::Error as JwtError, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize};
use swagger::auth::Authorization;
use crate::server_auth;
use rust_server_test::{ use rust_server_test::{
Api, Api,
AllOfGetResponse, AllOfGetResponse,
@ -116,7 +126,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<AllOfGetResponse, ApiError> context: &C) -> Result<AllOfGetResponse, ApiError>
{ {
info!("all_of_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("all_of_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// A dummy endpoint to make the spec valid. /// A dummy endpoint to make the spec valid.
@ -125,7 +135,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<DummyGetResponse, ApiError> context: &C) -> Result<DummyGetResponse, ApiError>
{ {
info!("dummy_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("dummy_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn dummy_put( async fn dummy_put(
@ -134,7 +144,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<DummyPutResponse, ApiError> context: &C) -> Result<DummyPutResponse, ApiError>
{ {
info!("dummy_put({:?}) - X-Span-ID: {:?}", nested_response, context.get().0.clone()); info!("dummy_put({:?}) - X-Span-ID: {:?}", nested_response, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Get a file /// Get a file
@ -143,7 +153,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FileResponseGetResponse, ApiError> context: &C) -> Result<FileResponseGetResponse, ApiError>
{ {
info!("file_response_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("file_response_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn get_structured_yaml( async fn get_structured_yaml(
@ -151,7 +161,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetStructuredYamlResponse, ApiError> context: &C) -> Result<GetStructuredYamlResponse, ApiError>
{ {
info!("get_structured_yaml() - X-Span-ID: {:?}", context.get().0.clone()); info!("get_structured_yaml() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Test HTML handling /// Test HTML handling
@ -161,7 +171,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<HtmlPostResponse, ApiError> context: &C) -> Result<HtmlPostResponse, ApiError>
{ {
info!("html_post(\"{}\") - X-Span-ID: {:?}", body, context.get().0.clone()); info!("html_post(\"{}\") - X-Span-ID: {:?}", body, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
async fn post_yaml( async fn post_yaml(
@ -170,7 +180,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<PostYamlResponse, ApiError> context: &C) -> Result<PostYamlResponse, ApiError>
{ {
info!("post_yaml(\"{}\") - X-Span-ID: {:?}", value, context.get().0.clone()); info!("post_yaml(\"{}\") - X-Span-ID: {:?}", value, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Get an arbitrary JSON blob. /// Get an arbitrary JSON blob.
@ -179,7 +189,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<RawJsonGetResponse, ApiError> context: &C) -> Result<RawJsonGetResponse, ApiError>
{ {
info!("raw_json_get() - X-Span-ID: {:?}", context.get().0.clone()); info!("raw_json_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
/// Send an arbitrary JSON blob /// Send an arbitrary JSON blob
@ -189,7 +199,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<SoloObjectPostResponse, ApiError> context: &C) -> Result<SoloObjectPostResponse, ApiError>
{ {
info!("solo_object_post({:?}) - X-Span-ID: {:?}", value, context.get().0.clone()); info!("solo_object_post({:?}) - X-Span-ID: {:?}", value, context.get().0.clone());
Err(ApiError("Generic failure".into())) Err(ApiError("Api-Error: Operation is NOT implemented".into()))
} }
} }

View File

@ -0,0 +1,127 @@
use swagger::{
ApiError,
auth::{Basic, Bearer},
Has,
XSpanIdString};
use rust_server_test::{AuthenticationApi, Claims};
use crate::server::Server;
use jsonwebtoken::{decode, errors as JwtError, decode_header, DecodingKey, TokenData, Validation};
use swagger::auth::Authorization;
use log::{error, debug};
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
// See https://docs.rs/env_logger/latest/env_logger/ for more details
/// Get a dummy claim with full permissions (all scopes) for testing purposes
fn full_permission_claim() -> Claims {
Claims {
sub: "tester@acme.com".to_owned(),
company: "ACME".to_owned(),
iss: "mini-bank-IDP".to_owned(),
aud: "org.acme.Resource_Server".to_string(),
// added a very long expiry time
exp: 10000000000,
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
scopes: [
].join(", ")
}
}
/// Extract the data from a Bearer token using the provided Key (secret) and using the HS512-algorithm in this example.
fn extract_token_data(token: &str, key: &[u8]) -> Result<TokenData<Claims>, JwtError::Error> {
// Ensure that you set the correct algorithm and correct key.
// See https://github.com/Keats/jsonwebtoken for more information.
let header = decode_header(token)?;
let validation = {
let mut validation = Validation::new(header.alg);
validation.set_audience(&["org.acme.Resource_Server"]);
validation.validate_exp = true;
validation
};
let token_data = decode::<Claims>(
&token,
&DecodingKey::from_secret(key),
&validation,
)?;
Ok(token_data)
}
/// Build a swagger-Authorization based on the claims (Assuming claims have been extracted from a validated token)
fn build_authorization(claims: Claims) -> Authorization {
let mut scopes = std::collections::BTreeSet::<String>::new();
claims
.scopes
.split(",")
.map(|s| s.trim())
.for_each(|s| {let _ = scopes.insert(s.to_string()); });
let scopes = swagger::auth::Scopes::Some(scopes);
Authorization{
subject: claims.sub,
scopes,
issuer: Some(claims.iss)}
}
fn get_jwt_error_string(error: JwtError::Error) -> String {
match error.kind() {
JwtError::ErrorKind::InvalidSignature => "Incorrect token signature".to_owned(),
JwtError::ErrorKind::InvalidAlgorithm => "The Algorithm is not correct".to_owned(),
JwtError::ErrorKind::ExpiredSignature => "The token has expired".to_owned(),
JwtError::ErrorKind::Base64(e) => format!("Base64 decode failed: {e}"),
JwtError::ErrorKind::Json(e) => format!("JSON decoding: {e}"),
JwtError::ErrorKind::Utf8(e) => format!("Invalid UTF-8: {e}"),
_ => error.to_string()
}
}
impl<C> AuthenticationApi for Server<C> where C: Has<XSpanIdString> + Send + Sync {
/// Implementation of the method to map a Bearer-token to an Authorization
fn bearer_authorization(&self, bearer: &Bearer) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Bearer-token, {bearer:#?}");
match extract_token_data(&bearer.token, b"secret") {
Ok(auth_data) => {
debug!("\tUnpack auth_data as: {auth_data:#?}");
let authorization = build_authorization(auth_data.claims);
Ok(authorization)
},
Err(err) => {
let msg = get_jwt_error_string(err);
error!("Failed to unpack Bearer-token: {msg}");
Err(ApiError(msg))
}
}
}
/// Implementation of the method to map an api-key to an Authorization
fn apikey_authorization(&self, api_key: &str) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received api-key, {api_key:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
/// Implementation of the method to map a basic authentication (username and password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError> {
debug!("\tAuthorizationApi: Received Basic-token, {basic:#?}");
// TODO: insert the logic to map received apikey to the set of claims
let claims = full_permission_claim();
// and build an authorization out of it
Ok(build_authorization(claims))
}
}

View File

@ -0,0 +1,62 @@
use std::collections::BTreeSet;
use crate::server::Authorization;
use serde::{Deserialize, Serialize};
use swagger::{ApiError, auth::{Basic, Bearer}};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub iss: String,
pub aud: String,
pub company: String,
pub exp: u64,
pub scopes: String,
}
pub trait AuthenticationApi {
/// Method should be implemented (see example-code) to map Bearer-token to an Authorization
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map ApiKey to an Authorization
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError>;
/// Method should be implemented (see example-code) to map Basic (Username:password) to an Authorization
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError>;
}
// Implement it for AllowAllAuthenticator (dummy is needed, but should not used as we have Bearer authorization)
use swagger::auth::{AllowAllAuthenticator, RcBound, Scopes};
fn dummy_authorization() -> Authorization {
// Is called when MakeAllowAllAuthenticator is added to the stack. This is not needed as we have Bearer-authorization in the example-code.
// However, if you want to use it anyway this can not be unimplemented, so dummy implementation added.
// unimplemented!()
Authorization{
subject: "Dummmy".to_owned(),
scopes: Scopes::Some(BTreeSet::new()), // create an empty scope, as this should not be used
issuer: None
}
}
impl<T, RC> AuthenticationApi for AllowAllAuthenticator<T, RC>
where
RC: RcBound,
RC::Result: Send + 'static {
/// Get method to map Bearer-token to an Authorization
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map api-key to an Authorization
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
/// Get method to map basic token to an Authorization
fn basic_authorization(&self, _basic: &Basic) -> Result<Authorization, ApiError> {
Ok(dummy_authorization())
}
}

View File

@ -8,7 +8,8 @@ use std::marker::PhantomData;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use swagger::auth::{AuthData, Authorization, Bearer, Scopes}; use swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString}; use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api; use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> { pub struct MakeAddContext<T, A> {
inner: T, inner: T,
@ -89,7 +90,7 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
B: Push<Option<AuthData>, Result=C>, B: Push<Option<AuthData>, Result=C>,
C: Push<Option<Authorization>, Result=D>, C: Push<Option<Authorization>, Result=D>,
D: Send + 'static, D: Send + 'static,
T: Service<(Request<ReqBody>, D)> T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{ {
type Error = T::Error; type Error = T::Error;
type Future = T::Future; type Future = T::Future;

Some files were not shown because too many files have changed in this diff Show More