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("models.mustache", "src", "models.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-server_auth.mustache", "src/server", "server_auth.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-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-auth.mustache", "examples/client", "client_auth.rs"));
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-key.pem", "examples", "server-key.pem"));

View File

@ -17,7 +17,7 @@ license = "{{.}}"
# Override this license by providing a License Object in the OpenAPI.
license = "Unlicense"
{{/licenseInfo}}
edition = "2021"
edition = "2018"
{{#publishRustRegistry}}
publish = ["{{.}}"]
{{/publishRustRegistry}}
@ -133,6 +133,9 @@ frunk_core = { version = "0.3.0", optional = true }
frunk-enum-derive = { 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]
clap = "2.25"
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 swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api;
use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> {
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>,
C: Push<Option<Authorization>, Result=D>,
D: Send + 'static,
T: Service<(Request<ReqBody>, D)>
T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{
type Error = T::Error;
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 std::ops::Deref;
if let Some(basic) = swagger::auth::from_headers::<Basic>(headers) {
let authorization = self.inner.basic_authorization(&basic);
let auth_data = AuthData::Basic(basic);
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))
}
@ -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 std::ops::Deref;
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
let authorization = self.inner.bearer_authorization(&bearer);
let auth_data = AuthData::Bearer(bearer);
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))
}
@ -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 std::ops::Deref;
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
let authorization = self.inner.bearer_authorization(&bearer);
let auth_data = AuthData::Bearer(bearer);
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))
}
@ -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;
if let Some(header) = api_key_from_header(headers, "{{{keyParamName}}}") {
let authorization = self.inner.apikey_authorization(&header);
let auth_data = AuthData::ApiKey(header);
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))
}
@ -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())
.next();
if let Some(key) = key {
let authorization = self.inner.apikey_authorization(&key);
let auth_data = AuthData::ApiKey(key);
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))
}

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)]
use futures::{future, Stream, stream};
#[allow(unused_imports)]
use {{{externCrateName}}}::{Api, ApiNoContext, Client, ContextWrapperExt, models,
use {{{externCrateName}}}::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
{{#apiInfo}}
{{#apis}}
{{#operations}}
@ -20,6 +20,9 @@ use {{{externCrateName}}}::{Api, ApiNoContext, Client, ContextWrapperExt, models
};
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)]
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);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples
#[allow(unused_mut)]
fn main() {
@ -69,6 +76,37 @@ fn main() {
.help("Port to contact"))
.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 base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" },
@ -76,7 +114,7 @@ fn main() {
matches.value_of("port").unwrap());
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") {
// 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 = 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)]
let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop {
if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
}
}
} else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP
hyper::server::Server::bind(&addr).serve(service).await.unwrap()
}

View File

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

View File

@ -14,5 +14,5 @@
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());
Err(ApiError("Generic failure".into()))
Err(ApiError("Api-Error: Operation is NOT implemented".into()))
}

View File

@ -1,5 +1,11 @@
{{>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}}}::{
Api,
{{#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(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 futures::Stream;
use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>;
@ -16,6 +19,10 @@ pub const BASE_PATH: &str = "{{{basePathWithoutHost}}}";
pub const API_VERSION: &str = "{{{.}}}";
{{/appVersion}}
mod auth;
pub use auth::{AuthenticationApi, Claims};
{{#apiInfo}}
{{#apis}}
{{#operations}}

View File

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

View File

@ -1,3 +1,4 @@
pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + '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
T: Api<C> + Clone + Send + '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}}
};
mod server_auth;
{{#hasCallbacks}}
pub mod callbacks;

View File

@ -224,11 +224,10 @@
let deserializer = &mut serde_json::Deserializer::from_slice(&body);
{{/x-consumes-json}}
{{^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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_{{{paramName}}}) => param_{{{paramName}}},
{{#required}}
Err(e) => return Ok(Response::builder()
@ -422,11 +421,10 @@
Some("{{{contentType}}}") if param_{{{paramName}}}.is_none() => {
// Extract JSON part.
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);
unused_elements.push(path.to_string());
};
let json_data: {{dataType}} = match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(json_data) => json_data,
Err(e) => return Ok(Response::builder()
.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/default_api.md
examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs
examples/server-chain.pem
examples/server-key.pem
examples/server/main.rs
examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs
src/context.rs
src/header.rs
src/lib.rs
src/models.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"
# Override this license by providing a License Object in the OpenAPI.
license = "Unlicense"
edition = "2021"
edition = "2018"
[features]
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-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies]
clap = "2.25"
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)]
use futures::{future, Stream, stream};
#[allow(unused_imports)]
use multipart_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models,
use multipart_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
MultipartRelatedRequestPostResponse,
MultipartRequestPostResponse,
MultipleIdenticalMimeTypesPostResponse,
};
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)]
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);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples
#[allow(unused_mut)]
fn main() {
@ -50,6 +57,32 @@ fn main() {
.help("Port to contact"))
.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 base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" },
@ -57,7 +90,7 @@ fn main() {
matches.value_of("port").unwrap());
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") {
// Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! Main binary entry point for multipart_v3 implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)]
use clap::{App, Arg};
mod server;
mod server_auth;
/// 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 = 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)]
let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop {
if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
}
}
} else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP
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::{
Api,
MultipartRelatedRequestPostResponse,
@ -113,7 +123,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
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());
Err(ApiError("Generic failure".into()))
Err(ApiError("Api-Error: Operation is NOT implemented".into()))
}
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>
{
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(
@ -135,7 +145,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MultipleIdenticalMimeTypesPostResponse, ApiError>
{
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 swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api;
use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> {
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>,
C: Push<Option<Authorization>, Result=D>,
D: Send + 'static,
T: Service<(Request<ReqBody>, D)>
T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{
type Error = T::Error;
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(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 futures::Stream;
use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = "";
pub const API_VERSION: &str = "1.0.7";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum MultipartRelatedRequestPostResponse {
/// OK

View File

@ -19,8 +19,7 @@ use multipart::server::Multipart;
use multipart::server::save::SaveResult;
#[allow(unused_imports)]
use crate::models;
use crate::header;
use crate::{models, header, AuthenticationApi};
pub use crate::context;
@ -32,6 +31,8 @@ use crate::{Api,
MultipleIdenticalMimeTypesPostResponse
};
mod server_auth;
mod paths {
use lazy_static::lazy_static;
@ -48,6 +49,7 @@ mod paths {
pub(crate) static ID_MULTIPLE_IDENTICAL_MIME_TYPES: usize = 2;
}
pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + '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
T: Api<C> + Clone + Send + '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() => {
// Extract JSON part.
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);
unused_elements.push(path.to_string());
};
let json_data: models::MultipartRequestObjectField = match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(json_data) => json_data,
Err(e) => return Ok(Response::builder()
.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/default_api.md
examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs
examples/server-chain.pem
examples/server-key.pem
examples/server/main.rs
examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs
src/context.rs
src/header.rs
src/lib.rs
src/models.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)"
# Override this license by providing a License Object in the OpenAPI.
license = "Unlicense"
edition = "2021"
edition = "2018"
[features]
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-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies]
clap = "2.25"
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)]
use futures::{future, Stream, stream};
#[allow(unused_imports)]
use no_example_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models,
use no_example_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
OpGetResponse,
};
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)]
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);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples
#[allow(unused_mut)]
fn main() {
@ -45,6 +52,32 @@ fn main() {
.help("Port to contact"))
.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 base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" },
@ -52,7 +85,7 @@ fn main() {
matches.value_of("port").unwrap());
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") {
// Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! Main binary entry point for no_example_v3 implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)]
use clap::{App, Arg};
mod server;
mod server_auth;
/// 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 = 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)]
let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop {
if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
}
}
} else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP
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::{
Api,
OpGetResponse,
@ -109,7 +119,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<OpGetResponse, ApiError>
{
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 swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api;
use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> {
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>,
C: Push<Option<Authorization>, Result=D>,
D: Send + 'static,
T: Service<(Request<ReqBody>, D)>
T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{
type Error = T::Error;
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(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 futures::Stream;
use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = "";
pub const API_VERSION: &str = "0.0.1";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum OpGetResponse {
/// OK

View File

@ -14,8 +14,7 @@ use swagger::auth::Scopes;
use url::form_urlencoded;
#[allow(unused_imports)]
use crate::models;
use crate::header;
use crate::{models, header, AuthenticationApi};
pub use crate::context;
@ -25,6 +24,8 @@ use crate::{Api,
OpGetResponse
};
mod server_auth;
mod paths {
use lazy_static::lazy_static;
@ -37,6 +38,7 @@ mod paths {
pub(crate) static ID_OP: usize = 0;
}
pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + '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
T: Api<C> + Clone + Send + '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 param_op_get_request: Option<models::OpGetRequest> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_op_get_request) => param_op_get_request,
Err(e) => return Ok(Response::builder()
.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/repo_api.md
examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs
examples/client/server.rs
examples/server-chain.pem
examples/server-key.pem
examples/server/main.rs
examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/callbacks.rs
src/client/mod.rs
src/context.rs
@ -51,3 +54,4 @@ src/lib.rs
src/models.rs
src/server/callbacks.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"
# Override this license by providing a License Object in the OpenAPI.
license = "Unlicense"
edition = "2021"
edition = "2018"
[features]
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-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies]
clap = "2.25"
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)]
use futures::{future, Stream, stream};
#[allow(unused_imports)]
use openapi_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models,
use openapi_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
AnyOfGetResponse,
CallbackWithHeaderPostResponse,
ComplexQueryParamGetResponse,
@ -35,6 +35,9 @@ use openapi_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models,
};
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)]
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);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples
#[allow(unused_mut)]
fn main() {
@ -96,6 +103,34 @@ fn main() {
.help("Port to contact"))
.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 base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" },
@ -103,7 +138,7 @@ fn main() {
matches.value_of("port").unwrap());
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") {
// Using Simple HTTPS

View File

@ -30,7 +30,9 @@ pub async fn create(addr: &str, https: bool) {
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)]
let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop {
if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
}
}
} else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP
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>
{
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(
@ -117,7 +121,7 @@ impl<C> CallbackApi<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CallbackCallbackPostResponse, ApiError>
{
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.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)]
use clap::{App, Arg};
mod server;
mod server_auth;
/// 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 = 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)]
let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop {
if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
}
}
} else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP
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::{
Api,
AnyOfGetResponse,
@ -134,7 +144,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<AnyOfGetResponse, ApiError>
{
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(
@ -143,7 +153,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CallbackWithHeaderPostResponse, ApiError>
{
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(
@ -152,7 +162,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<ComplexQueryParamGetResponse, ApiError>
{
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(
@ -161,7 +171,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<EnumInPathPathParamGetResponse, ApiError>
{
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(
@ -170,7 +180,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<JsonComplexQueryParamGetResponse, ApiError>
{
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(
@ -179,7 +189,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MandatoryRequestHeaderGetResponse, ApiError>
{
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(
@ -187,7 +197,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MergePatchJsonGetResponse, ApiError>
{
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.
@ -196,7 +206,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MultigetGetResponse, ApiError>
{
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(
@ -204,7 +214,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<MultipleAuthSchemeGetResponse, ApiError>
{
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(
@ -212,7 +222,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<OneOfGetResponse, ApiError>
{
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(
@ -220,7 +230,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<OverrideServerGetResponse, ApiError>
{
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.
@ -232,7 +242,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<ParamgetGetResponse, ApiError>
{
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(
@ -240,7 +250,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<ReadonlyAuthSchemeGetResponse, ApiError>
{
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(
@ -249,7 +259,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<RegisterCallbackPostResponse, ApiError>
{
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(
@ -258,7 +268,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<RequiredOctetStreamPutResponse, ApiError>
{
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(
@ -266,7 +276,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<ResponsesWithHeadersGetResponse, ApiError>
{
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(
@ -274,7 +284,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Rfc7807GetResponse, ApiError>
{
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(
@ -283,7 +293,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UntypedPropertyGetResponse, ApiError>
{
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(
@ -291,7 +301,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UuidGetResponse, ApiError>
{
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(
@ -300,7 +310,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<XmlExtraPostResponse, ApiError>
{
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(
@ -309,7 +319,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<XmlOtherPostResponse, ApiError>
{
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(
@ -318,7 +328,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<XmlOtherPutResponse, ApiError>
{
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
@ -328,7 +338,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<XmlPostResponse, ApiError>
{
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(
@ -337,7 +347,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<XmlPutResponse, ApiError>
{
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(
@ -346,7 +356,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CreateRepoResponse, ApiError>
{
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(
@ -355,7 +365,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetRepoInfoResponse, ApiError>
{
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;
#[allow(unused_imports)]
use crate::models;
use crate::header;
use crate::{models, header, AuthenticationApi};
pub use crate::context;
@ -52,6 +51,7 @@ mod paths {
}
pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + '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
T: Api<C> + Clone + Send + '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 swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api;
use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> {
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>,
C: Push<Option<Authorization>, Result=D>,
D: Send + 'static,
T: Service<(Request<ReqBody>, D)>
T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{
type Error = T::Error;
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 std::ops::Deref;
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
let authorization = self.inner.bearer_authorization(&bearer);
let auth_data = AuthData::Bearer(bearer);
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))
}

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(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 futures::Stream;
use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = "";
pub const API_VERSION: &str = "1.0.7";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[must_use]
pub enum AnyOfGetResponse {

View File

@ -14,8 +14,7 @@ use swagger::auth::Scopes;
use url::form_urlencoded;
#[allow(unused_imports)]
use crate::models;
use crate::header;
use crate::{models, header, AuthenticationApi};
pub use crate::context;
@ -50,6 +49,8 @@ use crate::{Api,
GetRepoInfoResponse
};
mod server_auth;
pub mod callbacks;
mod paths {
@ -122,6 +123,7 @@ mod paths {
pub(crate) static ID_XML_OTHER: usize = 23;
}
pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + '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
T: Api<C> + Clone + Send + '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 param_object_untyped_props: Option<models::ObjectUntypedProps> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_object_untyped_props) => param_object_untyped_props,
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 param_duplicate_xml_object: Option<models::DuplicateXmlObject> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_duplicate_xml_object) => param_duplicate_xml_object,
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 param_another_xml_object: Option<models::AnotherXmlObject> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_another_xml_object) => param_another_xml_object,
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 param_another_xml_array: Option<models::AnotherXmlArray> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_another_xml_array) => param_another_xml_array,
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 param_xml_array: Option<models::XmlArray> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_xml_array) => param_xml_array,
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 param_xml_object: Option<models::XmlObject> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_xml_object) => param_xml_object,
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 param_object_param: Option<models::ObjectParam> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_object_param) => param_object_param,
Err(e) => return Ok(Response::builder()
.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
docs/default_api.md
examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs
examples/server-chain.pem
examples/server-key.pem
examples/server/main.rs
examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs
src/context.rs
src/header.rs
src/lib.rs
src/models.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)"
# Override this license by providing a License Object in the OpenAPI.
license = "Unlicense"
edition = "2021"
edition = "2018"
[features]
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-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies]
clap = "2.25"
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)]
use futures::{future, Stream, stream};
#[allow(unused_imports)]
use ops_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models,
use ops_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
Op10GetResponse,
Op11GetResponse,
Op12GetResponse,
@ -45,6 +45,9 @@ use ops_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models,
};
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)]
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);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples
#[allow(unused_mut)]
fn main() {
@ -118,6 +125,32 @@ fn main() {
.help("Port to contact"))
.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 base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" },
@ -125,7 +158,7 @@ fn main() {
matches.value_of("port").unwrap());
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") {
// Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! Main binary entry point for ops_v3 implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)]
use clap::{App, Arg};
mod server;
mod server_auth;
/// 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 = 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)]
let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop {
if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
}
}
} else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP
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::{
Api,
Op10GetResponse,
@ -144,7 +154,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op10GetResponse, ApiError>
{
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(
@ -152,7 +162,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op11GetResponse, ApiError>
{
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(
@ -160,7 +170,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op12GetResponse, ApiError>
{
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(
@ -168,7 +178,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op13GetResponse, ApiError>
{
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(
@ -176,7 +186,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op14GetResponse, ApiError>
{
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(
@ -184,7 +194,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op15GetResponse, ApiError>
{
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(
@ -192,7 +202,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op16GetResponse, ApiError>
{
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(
@ -200,7 +210,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op17GetResponse, ApiError>
{
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(
@ -208,7 +218,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op18GetResponse, ApiError>
{
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(
@ -216,7 +226,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op19GetResponse, ApiError>
{
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(
@ -224,7 +234,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op1GetResponse, ApiError>
{
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(
@ -232,7 +242,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op20GetResponse, ApiError>
{
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(
@ -240,7 +250,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op21GetResponse, ApiError>
{
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(
@ -248,7 +258,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op22GetResponse, ApiError>
{
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(
@ -256,7 +266,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op23GetResponse, ApiError>
{
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(
@ -264,7 +274,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op24GetResponse, ApiError>
{
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(
@ -272,7 +282,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op25GetResponse, ApiError>
{
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(
@ -280,7 +290,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op26GetResponse, ApiError>
{
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(
@ -288,7 +298,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op27GetResponse, ApiError>
{
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(
@ -296,7 +306,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op28GetResponse, ApiError>
{
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(
@ -304,7 +314,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op29GetResponse, ApiError>
{
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(
@ -312,7 +322,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op2GetResponse, ApiError>
{
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(
@ -320,7 +330,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op30GetResponse, ApiError>
{
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(
@ -328,7 +338,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op31GetResponse, ApiError>
{
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(
@ -336,7 +346,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op32GetResponse, ApiError>
{
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(
@ -344,7 +354,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op33GetResponse, ApiError>
{
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(
@ -352,7 +362,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op34GetResponse, ApiError>
{
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(
@ -360,7 +370,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op35GetResponse, ApiError>
{
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(
@ -368,7 +378,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op36GetResponse, ApiError>
{
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(
@ -376,7 +386,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op37GetResponse, ApiError>
{
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(
@ -384,7 +394,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op3GetResponse, ApiError>
{
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(
@ -392,7 +402,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op4GetResponse, ApiError>
{
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(
@ -400,7 +410,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op5GetResponse, ApiError>
{
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(
@ -408,7 +418,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op6GetResponse, ApiError>
{
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(
@ -416,7 +426,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op7GetResponse, ApiError>
{
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(
@ -424,7 +434,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op8GetResponse, ApiError>
{
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(
@ -432,7 +442,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Op9GetResponse, ApiError>
{
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 swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api;
use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> {
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>,
C: Push<Option<Authorization>, Result=D>,
D: Send + 'static,
T: Service<(Request<ReqBody>, D)>
T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{
type Error = T::Error;
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(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 futures::Stream;
use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = "";
pub const API_VERSION: &str = "0.0.1";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum Op10GetResponse {
/// OK

View File

@ -14,8 +14,7 @@ use swagger::auth::Scopes;
use url::form_urlencoded;
#[allow(unused_imports)]
use crate::models;
use crate::header;
use crate::{models, header, AuthenticationApi};
pub use crate::context;
@ -61,6 +60,8 @@ use crate::{Api,
Op9GetResponse
};
mod server_auth;
mod paths {
use lazy_static::lazy_static;
@ -145,6 +146,7 @@ mod paths {
pub(crate) static ID_OP9: usize = 36;
}
pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + '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
T: Api<C> + Clone + Send + '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/user_api.md
examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs
examples/server-chain.pem
examples/server-key.pem
examples/server/main.rs
examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs
src/context.rs
src/header.rs
src/lib.rs
src/models.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"]
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"
edition = "2021"
edition = "2018"
publish = ["crates-io"]
[features]
@ -71,6 +71,9 @@ frunk_core = { version = "0.3.0", optional = true }
frunk-enum-derive = { 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]
clap = "2.25"
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)]
use futures::{future, Stream, stream};
#[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,
Call123exampleResponse,
FakeOuterBooleanSerializeResponse,
@ -43,6 +43,9 @@ use petstore_with_fake_endpoints_models_for_testing::{Api, ApiNoContext, Client,
};
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)]
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);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples
#[allow(unused_mut)]
fn main() {
@ -104,6 +111,34 @@ fn main() {
.help("Port to contact"))
.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 base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" },
@ -111,7 +146,7 @@ fn main() {
matches.value_of("port").unwrap());
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") {
// Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! 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)]
use clap::{App, Arg};
mod server;
mod server_auth;
/// 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 = 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)]
let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop {
if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
}
}
} else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP
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::{
Api,
TestSpecialTagsResponse,
@ -144,7 +154,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestSpecialTagsResponse, ApiError>
{
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(
@ -152,7 +162,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<Call123exampleResponse, ApiError>
{
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(
@ -161,7 +171,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FakeOuterBooleanSerializeResponse, ApiError>
{
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(
@ -170,7 +180,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FakeOuterCompositeSerializeResponse, ApiError>
{
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(
@ -179,7 +189,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FakeOuterNumberSerializeResponse, ApiError>
{
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(
@ -188,7 +198,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FakeOuterStringSerializeResponse, ApiError>
{
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(
@ -196,7 +206,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FakeResponseWithNumericalDescriptionResponse, ApiError>
{
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(
@ -205,7 +215,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<HyphenParamResponse, ApiError>
{
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(
@ -215,7 +225,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestBodyWithQueryParamsResponse, ApiError>
{
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
@ -225,7 +235,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestClientModelResponse, ApiError>
{
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 假端點 偽のエンドポイント 가짜 엔드 포인트
@ -248,7 +258,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
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());
Err(ApiError("Generic failure".into()))
Err(ApiError("Api-Error: Operation is NOT implemented".into()))
}
/// 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>
{
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
@ -274,7 +284,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestInlineAdditionalPropertiesResponse, ApiError>
{
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
@ -285,7 +295,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestJsonFormDataResponse, ApiError>
{
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
@ -295,7 +305,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<TestClassnameResponse, ApiError>
{
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
@ -305,7 +315,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<AddPetResponse, ApiError>
{
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
@ -316,7 +326,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<DeletePetResponse, ApiError>
{
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
@ -326,7 +336,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FindPetsByStatusResponse, ApiError>
{
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
@ -336,7 +346,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FindPetsByTagsResponse, ApiError>
{
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
@ -346,7 +356,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetPetByIdResponse, ApiError>
{
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
@ -356,7 +366,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UpdatePetResponse, ApiError>
{
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
@ -368,7 +378,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UpdatePetWithFormResponse, ApiError>
{
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
@ -380,7 +390,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UploadFileResponse, ApiError>
{
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
@ -390,7 +400,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<DeleteOrderResponse, ApiError>
{
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
@ -399,7 +409,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetInventoryResponse, ApiError>
{
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
@ -409,7 +419,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetOrderByIdResponse, ApiError>
{
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
@ -419,7 +429,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<PlaceOrderResponse, ApiError>
{
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
@ -429,7 +439,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CreateUserResponse, ApiError>
{
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
@ -439,7 +449,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CreateUsersWithArrayInputResponse, ApiError>
{
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
@ -449,7 +459,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<CreateUsersWithListInputResponse, ApiError>
{
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
@ -459,7 +469,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<DeleteUserResponse, ApiError>
{
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
@ -469,7 +479,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetUserByNameResponse, ApiError>
{
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
@ -480,7 +490,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<LoginUserResponse, ApiError>
{
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
@ -489,7 +499,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<LogoutUserResponse, ApiError>
{
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
@ -500,7 +510,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<UpdateUserResponse, ApiError>
{
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 swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api;
use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> {
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>,
C: Push<Option<Authorization>, Result=D>,
D: Send + 'static,
T: Service<(Request<ReqBody>, D)>
T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{
type Error = T::Error;
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 std::ops::Deref;
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
let authorization = self.inner.bearer_authorization(&bearer);
let auth_data = AuthData::Bearer(bearer);
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))
}
@ -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;
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 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))
}
@ -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())
.next();
if let Some(key) = key {
let authorization = self.inner.apikey_authorization(&key);
let auth_data = AuthData::ApiKey(key);
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))
}
@ -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 std::ops::Deref;
if let Some(basic) = swagger::auth::from_headers::<Basic>(headers) {
let authorization = self.inner.basic_authorization(&basic);
let auth_data = AuthData::Basic(basic);
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))
}

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(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 futures::Stream;
use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = "/v2";
pub const API_VERSION: &str = "1.0.0";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum TestSpecialTagsResponse {
/// successful operation

View File

@ -16,8 +16,7 @@ use multipart::server::Multipart;
use multipart::server::save::SaveResult;
#[allow(unused_imports)]
use crate::models;
use crate::header;
use crate::{models, header, AuthenticationApi};
pub use crate::context;
@ -61,6 +60,8 @@ use crate::{Api,
UpdateUserResponse
};
mod server_auth;
mod paths {
use lazy_static::lazy_static;
@ -155,6 +156,7 @@ mod paths {
}
}
pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + '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
T: Api<C> + Clone + Send + '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 param_body: Option<models::Client> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder()
.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 param_body: Option<models::OuterBoolean> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
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 param_body: Option<models::OuterComposite> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
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 param_body: Option<models::OuterNumber> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
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 param_body: Option<models::OuterString> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
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 param_body: Option<models::User> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder()
.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 param_body: Option<models::Client> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder()
.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 param_param: Option<std::collections::HashMap<String, String>> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_param) => param_param,
Err(e) => return Ok(Response::builder()
.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 param_body: Option<models::Client> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder()
.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 param_body: Option<models::Pet> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder()
.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 param_body: Option<models::Pet> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder()
.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 param_body: Option<models::Order> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder()
.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 param_body: Option<models::User> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder()
.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 param_body: Option<Vec<models::User>> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder()
.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 param_body: Option<Vec<models::User>> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder()
.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 param_body: Option<models::User> = if !body.is_empty() {
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);
unused_elements.push(path.to_string());
};
match serde_ignored::deserialize(deserializer, handle_unknown_field) {
}) {
Ok(param_body) => param_body,
Err(e) => return Ok(Response::builder()
.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
docs/default_api.md
examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs
examples/server-chain.pem
examples/server-key.pem
examples/server/main.rs
examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs
src/context.rs
src/header.rs
src/lib.rs
src/models.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)"
# Override this license by providing a License Object in the OpenAPI.
license = "Unlicense"
edition = "2021"
edition = "2018"
[features]
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-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies]
clap = "2.25"
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)]
use futures::{future, Stream, stream};
#[allow(unused_imports)]
use ping_bearer_auth::{Api, ApiNoContext, Client, ContextWrapperExt, models,
use ping_bearer_auth::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
PingGetResponse,
};
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)]
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);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples
#[allow(unused_mut)]
fn main() {
@ -46,6 +53,32 @@ fn main() {
.help("Port to contact"))
.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 base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" },
@ -53,7 +86,7 @@ fn main() {
matches.value_of("port").unwrap());
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") {
// Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! Main binary entry point for ping_bearer_auth implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)]
use clap::{App, Arg};
mod server;
mod server_auth;
/// 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 = 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)]
let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop {
if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
}
}
} else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP
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::{
Api,
PingGetResponse,
@ -108,7 +118,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<PingGetResponse, ApiError>
{
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 swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api;
use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> {
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>,
C: Push<Option<Authorization>, Result=D>,
D: Send + 'static,
T: Service<(Request<ReqBody>, D)>
T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{
type Error = T::Error;
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 std::ops::Deref;
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
let authorization = self.inner.bearer_authorization(&bearer);
let auth_data = AuthData::Bearer(bearer);
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))
}

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(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 futures::Stream;
use std::error::Error;
use std::collections::BTreeSet;
use std::task::{Poll, Context};
use swagger::{ApiError, ContextWrapper};
use serde::{Serialize, Deserialize};
use crate::server::Authorization;
type ServiceError = Box<dyn Error + Send + Sync + 'static>;
pub const BASE_PATH: &str = "";
pub const API_VERSION: &str = "1.0";
mod auth;
pub use auth::{AuthenticationApi, Claims};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum PingGetResponse {
/// OK

View File

@ -14,8 +14,7 @@ use swagger::auth::Scopes;
use url::form_urlencoded;
#[allow(unused_imports)]
use crate::models;
use crate::header;
use crate::{models, header, AuthenticationApi};
pub use crate::context;
@ -25,6 +24,8 @@ use crate::{Api,
PingGetResponse
};
mod server_auth;
mod paths {
use lazy_static::lazy_static;
@ -37,6 +38,7 @@ mod paths {
pub(crate) static ID_PING: usize = 0;
}
pub struct MakeService<T, C> where
T: Api<C> + Clone + Send + '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
T: Api<C> + Clone + Send + '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/default_api.md
examples/ca.pem
examples/client/client_auth.rs
examples/client/main.rs
examples/server-chain.pem
examples/server-key.pem
examples/server/main.rs
examples/server/server.rs
examples/server/server_auth.rs
src/auth.rs
src/client/mod.rs
src/context.rs
src/header.rs
src/lib.rs
src/models.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"
# Override this license by providing a License Object in the OpenAPI.
license = "Unlicense"
edition = "2021"
edition = "2018"
[features]
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-core = { version = "0.2.0", optional = true }
# Bearer authentication
jsonwebtoken = { version = "9.3.0", optional = false }
[dev-dependencies]
clap = "2.25"
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)]
use futures::{future, Stream, stream};
#[allow(unused_imports)]
use rust_server_test::{Api, ApiNoContext, Client, ContextWrapperExt, models,
use rust_server_test::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
AllOfGetResponse,
DummyGetResponse,
DummyPutResponse,
@ -17,6 +17,9 @@ use rust_server_test::{Api, ApiNoContext, Client, ContextWrapperExt, models,
};
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)]
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);
mod client_auth;
use client_auth::build_token;
// rt may be unused if there are no examples
#[allow(unused_mut)]
fn main() {
@ -60,6 +67,32 @@ fn main() {
.help("Port to contact"))
.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 base_url = format!("{}://{}:{}",
if is_https { "https" } else { "http" },
@ -67,7 +100,7 @@ fn main() {
matches.value_of("port").unwrap());
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") {
// Using Simple HTTPS

View File

@ -1,10 +1,13 @@
//! Main binary entry point for rust_server_test implementation.
// This is the amended version that adds Authorization via Inversion of Control.
#![allow(missing_docs)]
use clap::{App, Arg};
mod server;
mod server_auth;
/// 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 = 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)]
let mut service =
@ -56,6 +58,7 @@ pub async fn create(addr: &str, https: bool) {
let tls_acceptor = ssl.build();
let tcp_listener = TcpListener::bind(&addr).await.unwrap();
info!("Starting a server (with https)");
loop {
if let Ok((tcp, _)) = tcp_listener.accept().await {
let ssl = Ssl::new(tls_acceptor.context()).unwrap();
@ -75,6 +78,7 @@ pub async fn create(addr: &str, https: bool) {
}
}
} else {
info!("Starting a server (over http, so no TLS)");
// Using HTTP
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::{
Api,
AllOfGetResponse,
@ -116,7 +126,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<AllOfGetResponse, ApiError>
{
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.
@ -125,7 +135,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<DummyGetResponse, ApiError>
{
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(
@ -134,7 +144,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<DummyPutResponse, ApiError>
{
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
@ -143,7 +153,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<FileResponseGetResponse, ApiError>
{
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(
@ -151,7 +161,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<GetStructuredYamlResponse, ApiError>
{
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
@ -161,7 +171,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<HtmlPostResponse, ApiError>
{
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(
@ -170,7 +180,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<PostYamlResponse, ApiError>
{
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.
@ -179,7 +189,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<RawJsonGetResponse, ApiError>
{
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
@ -189,7 +199,7 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
context: &C) -> Result<SoloObjectPostResponse, ApiError>
{
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 swagger::auth::{AuthData, Authorization, Bearer, Scopes};
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
use crate::Api;
use crate::{Api, AuthenticationApi};
use log::error;
pub struct MakeAddContext<T, A> {
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>,
C: Push<Option<Authorization>, Result=D>,
D: Send + 'static,
T: Service<(Request<ReqBody>, D)>
T: Service<(Request<ReqBody>, D)> + AuthenticationApi
{
type Error = T::Error;
type Future = T::Future;

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