Skip to content

Commit 5e8b589

Browse files
authored
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
1 parent 0daf9ff commit 5e8b589

File tree

103 files changed

+2931
-465
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+2931
-465
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,15 @@ public RustServerCodegen() {
230230
supportingFiles.add(new SupportingFile("context.mustache", "src", "context.rs"));
231231
supportingFiles.add(new SupportingFile("models.mustache", "src", "models.rs"));
232232
supportingFiles.add(new SupportingFile("header.mustache", "src", "header.rs"));
233+
supportingFiles.add(new SupportingFile("auth.mustache", "src", "auth.rs"));
233234
supportingFiles.add(new SupportingFile("server-mod.mustache", "src/server", "mod.rs"));
235+
supportingFiles.add(new SupportingFile("server-server_auth.mustache", "src/server", "server_auth.rs"));
234236
supportingFiles.add(new SupportingFile("client-mod.mustache", "src/client", "mod.rs"));
235237
supportingFiles.add(new SupportingFile("example-server-main.mustache", "examples/server", "main.rs"));
236238
supportingFiles.add(new SupportingFile("example-server-server.mustache", "examples/server", "server.rs"));
239+
supportingFiles.add(new SupportingFile("example-server-auth.mustache", "examples/server", "server_auth.rs"));
237240
supportingFiles.add(new SupportingFile("example-client-main.mustache", "examples/client", "main.rs"));
241+
supportingFiles.add(new SupportingFile("example-client-auth.mustache", "examples/client", "client_auth.rs"));
238242
supportingFiles.add(new SupportingFile("example-ca.pem", "examples", "ca.pem"));
239243
supportingFiles.add(new SupportingFile("example-server-chain.pem", "examples", "server-chain.pem"));
240244
supportingFiles.add(new SupportingFile("example-server-key.pem", "examples", "server-key.pem"));

modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ license = "{{.}}"
1717
# Override this license by providing a License Object in the OpenAPI.
1818
license = "Unlicense"
1919
{{/licenseInfo}}
20-
edition = "2021"
20+
edition = "2018"
2121
{{#publishRustRegistry}}
2222
publish = ["{{.}}"]
2323
{{/publishRustRegistry}}
@@ -133,6 +133,9 @@ frunk_core = { version = "0.3.0", optional = true }
133133
frunk-enum-derive = { version = "0.2.0", optional = true }
134134
frunk-enum-core = { version = "0.2.0", optional = true }
135135

136+
# Bearer authentication
137+
jsonwebtoken = { version = "9.3.0", optional = false }
138+
136139
[dev-dependencies]
137140
clap = "2.25"
138141
env_logger = "0.7"
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use std::collections::BTreeSet;
2+
use crate::server::Authorization;
3+
use serde::{Deserialize, Serialize};
4+
use swagger::{ApiError, auth::{Basic, Bearer}};
5+
6+
#[derive(Debug, Serialize, Deserialize)]
7+
pub struct Claims {
8+
pub sub: String,
9+
pub iss: String,
10+
pub aud: String,
11+
pub company: String,
12+
pub exp: u64,
13+
pub scopes: String,
14+
}
15+
16+
17+
pub trait AuthenticationApi {
18+
19+
/// Method should be implemented (see example-code) to map Bearer-token to an Authorization
20+
fn bearer_authorization(&self, token: &Bearer) -> Result<Authorization, ApiError>;
21+
22+
/// Method should be implemented (see example-code) to map ApiKey to an Authorization
23+
fn apikey_authorization(&self, token: &str) -> Result<Authorization, ApiError>;
24+
25+
/// Method should be implemented (see example-code) to map Basic (Username:password) to an Authorization
26+
fn basic_authorization(&self, basic: &Basic) -> Result<Authorization, ApiError>;
27+
}
28+
29+
// Implement it for AllowAllAuthenticator (dummy is needed, but should not used as we have Bearer authorization)
30+
use swagger::auth::{AllowAllAuthenticator, RcBound, Scopes};
31+
32+
fn dummy_authorization() -> Authorization {
33+
// Is called when MakeAllowAllAuthenticator is added to the stack. This is not needed as we have Bearer-authorization in the example-code.
34+
// However, if you want to use it anyway this can not be unimplemented, so dummy implementation added.
35+
// unimplemented!()
36+
Authorization{
37+
subject: "Dummmy".to_owned(),
38+
scopes: Scopes::Some(BTreeSet::new()), // create an empty scope, as this should not be used
39+
issuer: None
40+
}
41+
}
42+
43+
impl<T, RC> AuthenticationApi for AllowAllAuthenticator<T, RC>
44+
where
45+
RC: RcBound,
46+
RC::Result: Send + 'static {
47+
48+
/// Get method to map Bearer-token to an Authorization
49+
fn bearer_authorization(&self, _token: &Bearer) -> Result<Authorization, ApiError> {
50+
Ok(dummy_authorization())
51+
}
52+
53+
/// Get method to map api-key to an Authorization
54+
fn apikey_authorization(&self, _apikey: &str) -> Result<Authorization, ApiError> {
55+
Ok(dummy_authorization())
56+
}
57+
58+
/// Get method to map basic token to an Authorization
59+
fn basic_authorization(&self, _basic: &Basic) -> Result<Authorization, ApiError> {
60+
Ok(dummy_authorization())
61+
}
62+
}

modules/openapi-generator/src/main/resources/rust-server/context.mustache

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use std::marker::PhantomData;
88
use std::task::{Poll, Context};
99
use swagger::auth::{AuthData, Authorization, Bearer, Scopes};
1010
use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString};
11-
use crate::Api;
11+
use crate::{Api, AuthenticationApi};
12+
use log::error;
1213

1314
pub struct MakeAddContext<T, A> {
1415
inner: T,
@@ -89,7 +90,7 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
8990
B: Push<Option<AuthData>, Result=C>,
9091
C: Push<Option<Authorization>, Result=D>,
9192
D: Send + 'static,
92-
T: Service<(Request<ReqBody>, D)>
93+
T: Service<(Request<ReqBody>, D)> + AuthenticationApi
9394
{
9495
type Error = T::Error;
9596
type Future = T::Future;
@@ -111,9 +112,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
111112
use swagger::auth::Basic;
112113
use std::ops::Deref;
113114
if let Some(basic) = swagger::auth::from_headers::<Basic>(headers) {
115+
let authorization = self.inner.basic_authorization(&basic);
114116
let auth_data = AuthData::Basic(basic);
117+
115118
let context = context.push(Some(auth_data));
116-
let context = context.push(None::<Authorization>);
119+
let context = match authorization {
120+
Ok(auth) => context.push(Some(auth)),
121+
Err(err) => {
122+
error!("Error during Authorization: {err:?}");
123+
context.push(None::<Authorization>)
124+
}
125+
};
117126
118127
return self.inner.call((request, context))
119128
}
@@ -124,9 +133,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
124133
use swagger::auth::Bearer;
125134
use std::ops::Deref;
126135
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
136+
let authorization = self.inner.bearer_authorization(&bearer);
127137
let auth_data = AuthData::Bearer(bearer);
138+
128139
let context = context.push(Some(auth_data));
129-
let context = context.push(None::<Authorization>);
140+
let context = match authorization {
141+
Ok(auth) => context.push(Some(auth)),
142+
Err(err) => {
143+
error!("Error during Authorization: {err:?}");
144+
context.push(None::<Authorization>)
145+
}
146+
};
130147
131148
return self.inner.call((request, context))
132149
}
@@ -138,9 +155,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
138155
use swagger::auth::Bearer;
139156
use std::ops::Deref;
140157
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(headers) {
158+
let authorization = self.inner.bearer_authorization(&bearer);
141159
let auth_data = AuthData::Bearer(bearer);
160+
142161
let context = context.push(Some(auth_data));
143-
let context = context.push(None::<Authorization>);
162+
let context = match authorization {
163+
Ok(auth) => context.push(Some(auth)),
164+
Err(err) => {
165+
error!("Error during Authorization: {err:?}");
166+
context.push(None::<Authorization>)
167+
}
168+
};
144169
145170
return self.inner.call((request, context))
146171
}
@@ -152,9 +177,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
152177
use swagger::auth::api_key_from_header;
153178
154179
if let Some(header) = api_key_from_header(headers, "{{{keyParamName}}}") {
180+
let authorization = self.inner.apikey_authorization(&header);
155181
let auth_data = AuthData::ApiKey(header);
182+
156183
let context = context.push(Some(auth_data));
157-
let context = context.push(None::<Authorization>);
184+
let context = match authorization {
185+
Ok(auth) => context.push(Some(auth)),
186+
Err(err) => {
187+
error!("Error during Authorization: {err:?}");
188+
context.push(None::<Authorization>)
189+
}
190+
};
158191
159192
return self.inner.call((request, context))
160193
}
@@ -167,9 +200,17 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
167200
.map(|e| e.1.clone().into_owned())
168201
.next();
169202
if let Some(key) = key {
203+
let authorization = self.inner.apikey_authorization(&key);
170204
let auth_data = AuthData::ApiKey(key);
205+
171206
let context = context.push(Some(auth_data));
172-
let context = context.push(None::<Authorization>);
207+
let context = match authorization {
208+
Ok(auth) => context.push(Some(auth)),
209+
Err(err) => {
210+
error!("Error during Authorization: {err:?}");
211+
context.push(None::<Authorization>)
212+
}
213+
};
173214
174215
return self.inner.call((request, context))
175216
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use {{{externCrateName}}}::Claims;
2+
use jsonwebtoken::{encode, errors::Error as JwtError, Algorithm, EncodingKey, Header};
3+
use log::debug;
4+
5+
/// build an encrypted token with the provided claims.
6+
pub fn build_token(my_claims: Claims, key: &[u8]) -> Result<String, JwtError> {
7+
8+
// Ensure that you set the correct algorithm and correct key.
9+
// See https://github.com/Keats/jsonwebtoken for more information.
10+
let header =
11+
Header { kid: Some("signing_key".to_owned()), alg: Algorithm::HS512, ..Default::default() };
12+
13+
let token = encode(&header, &my_claims, &EncodingKey::from_secret(key))?;
14+
debug!("Derived token: {:?}", token);
15+
16+
Ok(token)
17+
}

modules/openapi-generator/src/main/resources/rust-server/example-client-main.mustache

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ mod server;
77
#[allow(unused_imports)]
88
use futures::{future, Stream, stream};
99
#[allow(unused_imports)]
10-
use {{{externCrateName}}}::{Api, ApiNoContext, Client, ContextWrapperExt, models,
10+
use {{{externCrateName}}}::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
1111
{{#apiInfo}}
1212
{{#apis}}
1313
{{#operations}}
@@ -20,6 +20,9 @@ use {{{externCrateName}}}::{Api, ApiNoContext, Client, ContextWrapperExt, models
2020
};
2121
use clap::{App, Arg};
2222

23+
// NOTE: Set environment variable RUST_LOG to the name of the executable (or "cargo run") to activate console logging for all loglevels.
24+
// See https://docs.rs/env_logger/latest/env_logger/ for more details
25+
2326
#[allow(unused_imports)]
2427
use log::info;
2528

@@ -29,6 +32,10 @@ use swagger::{AuthData, ContextBuilder, EmptyContext, Has, Push, XSpanIdString};
2932

3033
type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option<AuthData>, XSpanIdString);
3134

35+
mod client_auth;
36+
use client_auth::build_token;
37+
38+
3239
// rt may be unused if there are no examples
3340
#[allow(unused_mut)]
3441
fn main() {
@@ -44,7 +51,7 @@ fn main() {
4451
{{#operation}}
4552
{{#vendorExtensions}}
4653
{{^x-no-client-example}}
47-
"{{{operationId}}}",
54+
"{{{operationId}}}",
4855
{{/x-no-client-example}}
4956
{{/vendorExtensions}}
5057
{{/operation}}
@@ -69,14 +76,45 @@ fn main() {
6976
.help("Port to contact"))
7077
.get_matches();
7178

79+
// Create Bearer-token with a fixed key (secret) for test purposes.
80+
// In a real (production) system this Bearer token should be obtained via an external Identity/Authentication-server
81+
// Ensure that you set the correct algorithm and encodingkey that matches what is used on the server side.
82+
// See https://github.com/Keats/jsonwebtoken for more information
83+
84+
let auth_token = build_token(
85+
Claims {
86+
sub: "tester@acme.com".to_owned(),
87+
company: "ACME".to_owned(),
88+
iss: "my_identity_provider".to_owned(),
89+
// added a very long expiry time
90+
aud: "org.acme.Resource_Server".to_string(),
91+
exp: 10000000000,
92+
// In this example code all available Scopes are added, so the current Bearer Token gets fully authorization.
93+
scopes: [
94+
{{#authMethods}}
95+
{{#scopes}}
96+
"{{{scope}}}",
97+
{{/scopes}}
98+
{{/authMethods}}
99+
].join(", ")
100+
},
101+
b"secret").unwrap();
102+
103+
let auth_data = if !auth_token.is_empty() {
104+
Some(AuthData::Bearer(swagger::auth::Bearer { token: auth_token}))
105+
} else {
106+
// No Bearer-token available, so return None
107+
None
108+
};
109+
72110
let is_https = matches.is_present("https");
73111
let base_url = format!("{}://{}:{}",
74-
if is_https { "https" } else { "http" },
75-
matches.value_of("host").unwrap(),
76-
matches.value_of("port").unwrap());
112+
if is_https { "https" } else { "http" },
113+
matches.value_of("host").unwrap(),
114+
matches.value_of("port").unwrap());
77115

78116
let context: ClientContext =
79-
swagger::make_context!(ContextBuilder, EmptyContext, None as Option<AuthData>, XSpanIdString::default());
117+
swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default());
80118

81119
let mut client : Box<dyn ApiNoContext<ClientContext>> = if matches.is_present("https") {
82120
// Using Simple HTTPS

0 commit comments

Comments
 (0)