Skip to content

Commit

Permalink
Adding support for empty security {} on noauth.
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinswiber committed Nov 25, 2023
1 parent a04a597 commit 6ee4e63
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 143 deletions.
312 changes: 172 additions & 140 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use convert_case::{Case, Casing};
#[cfg(target_arch = "wasm32")]
use gloo_utils::format::JsValueSerdeExt;
use indexmap::{IndexMap, IndexSet};
use openapi::v3_0::{self as openapi3, ObjectOrReference, Parameter};
use openapi::v3_0::{self as openapi3, ObjectOrReference, Parameter, SecurityRequirement};
use postman::AuthType;
use std::collections::BTreeMap;
#[cfg(target_arch = "wasm32")]
Expand Down Expand Up @@ -163,146 +163,15 @@ impl<'a> Transpiler<'a> {
};

if let Some(auth) = spec.auth {
let mut security_schemes = BTreeMap::new();
let security = match auth.auth_type {
AuthType::Basic => {
let scheme = openapi3::SecurityScheme::Http {
scheme: "basic".to_string(),
bearer_format: None,
};
let name = "basicAuth".to_string();
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some((name, vec![]))
}
AuthType::Digest => {
let scheme = openapi3::SecurityScheme::Http {
scheme: "digest".to_string(),
bearer_format: None,
};
let name = "digestAuth".to_string();
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some((name, vec![]))
}
AuthType::Bearer => {
let scheme = openapi3::SecurityScheme::Http {
scheme: "bearer".to_string(),
bearer_format: None,
};
let name = "bearerAuth".to_string();
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some((name, vec![]))
}
AuthType::Jwt => {
let scheme = openapi3::SecurityScheme::Http {
scheme: "bearer".to_string(),
bearer_format: Some("jwt".to_string()),
};
let name = "jwtBearerAuth".to_string();
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some((name, vec![]))
}
AuthType::Apikey => {
let name = "apiKey".to_string();
if let Some(apikey) = auth.apikey {
let scheme = openapi3::SecurityScheme::ApiKey {
name: transpiler.resolve_variables(
&apikey.key.unwrap_or("Authorization".to_string()),
VAR_REPLACE_CREDITS,
),
location: match apikey.location {
postman::ApiKeyLocation::Header => "header".to_string(),
postman::ApiKeyLocation::Query => "query".to_string(),
},
};
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
} else {
let scheme = openapi3::SecurityScheme::ApiKey {
name: "Authorization".to_string(),
location: "header".to_string(),
};
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
}
Some((name, vec![]))
}
AuthType::Oauth2 => {
let name = "oauth2".to_string();
if let Some(oauth2) = auth.oauth2 {
let mut flows: openapi3::Flows = Default::default();
let scopes = BTreeMap::from_iter(
oauth2
.scope
.clone()
.unwrap_or_default()
.iter()
.map(|s| transpiler.resolve_variables(s, VAR_REPLACE_CREDITS))
.map(|s| (s.to_string(), s.to_string())),
);
let authorization_url = transpiler.resolve_variables(
&oauth2.auth_url.unwrap_or("".to_string()),
VAR_REPLACE_CREDITS,
);
let token_url = transpiler.resolve_variables(
&oauth2.access_token_url.unwrap_or("".to_string()),
VAR_REPLACE_CREDITS,
);
let refresh_url = oauth2
.refresh_token_url
.map(|url| transpiler.resolve_variables(&url, VAR_REPLACE_CREDITS));
match oauth2.grant_type {
postman::Oauth2GrantType::AuthorizationCode
| postman::Oauth2GrantType::AuthorizationCodeWithPkce => {
flows.authorization_code = Some(openapi3::AuthorizationCodeFlow {
authorization_url,
token_url,
refresh_url,
scopes,
});
}
postman::Oauth2GrantType::ClientCredentials => {
flows.client_credentials = Some(openapi3::ClientCredentialsFlow {
token_url,
refresh_url,
scopes,
});
}
postman::Oauth2GrantType::PasswordCredentials => {
flows.password = Some(openapi3::PasswordFlow {
token_url,
refresh_url,
scopes,
});
}
postman::Oauth2GrantType::Implicit => {
flows.implicit = Some(openapi3::ImplicitFlow {
authorization_url,
refresh_url,
scopes,
});
}
}
let scheme = openapi3::SecurityScheme::OAuth2 {
flows: Box::new(flows),
};
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some((name, oauth2.scope.unwrap_or_default()))
} else {
let scheme = openapi3::SecurityScheme::OAuth2 {
flows: Default::default(),
};
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some((name, vec![]))
}
let security = transpiler.transform_security(&mut state, &auth);
if let Some(pair) = security {
if let Some((name, scopes)) = pair {
state.oas.security = Some(vec![SecurityRequirement {
requirement: Some(BTreeMap::from([(name, scopes)])),
}]);
} else {
state.oas.security = Some(vec![SecurityRequirement { requirement: None }]);
}
_ => None,
};

state.oas.components = Some(openapi3::Components {
security_schemes: Some(security_schemes),
..Default::default()
});

if let Some((name, scopes)) = security {
state.oas.security = Some(BTreeMap::from([(name, scopes)]));
}
}

Expand Down Expand Up @@ -787,6 +656,169 @@ impl<'a> Transpiler<'a> {
}
}

fn transform_security(
&self,
state: &mut TranspileState,
auth: &postman::Auth,
) -> Option<Option<(String, Vec<String>)>> {
if state.oas.components.is_none() {
state.oas.components = Some(openapi3::Components::default());
}
if state
.oas
.components
.as_ref()
.unwrap()
.security_schemes
.is_none()
{
state.oas.components.as_mut().unwrap().security_schemes = Some(BTreeMap::new());
}
let security_schemes = state
.oas
.components
.as_mut()
.unwrap()
.security_schemes
.as_mut()
.unwrap();
let security = match auth.auth_type {
AuthType::Noauth => Some(None),
AuthType::Basic => {
let scheme = openapi3::SecurityScheme::Http {
scheme: "basic".to_string(),
bearer_format: None,
};
let name = "basicAuth".to_string();
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some(Some((name, vec![])))
}
AuthType::Digest => {
let scheme = openapi3::SecurityScheme::Http {
scheme: "digest".to_string(),
bearer_format: None,
};
let name = "digestAuth".to_string();
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some(Some((name, vec![])))
}
AuthType::Bearer => {
let scheme = openapi3::SecurityScheme::Http {
scheme: "bearer".to_string(),
bearer_format: None,
};
let name = "bearerAuth".to_string();
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some(Some((name, vec![])))
}
AuthType::Jwt => {
let scheme = openapi3::SecurityScheme::Http {
scheme: "bearer".to_string(),
bearer_format: Some("jwt".to_string()),
};
let name = "jwtBearerAuth".to_string();
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some(Some((name, vec![])))
}
AuthType::Apikey => {
let name = "apiKey".to_string();
if let Some(apikey) = &auth.apikey {
let scheme = openapi3::SecurityScheme::ApiKey {
name: self.resolve_variables(
apikey.key.as_ref().unwrap_or(&"Authorization".to_string()),
VAR_REPLACE_CREDITS,
),
location: match apikey.location {
postman::ApiKeyLocation::Header => "header".to_string(),
postman::ApiKeyLocation::Query => "query".to_string(),
},
};
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
} else {
let scheme = openapi3::SecurityScheme::ApiKey {
name: "Authorization".to_string(),
location: "header".to_string(),
};
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
}
Some(Some((name, vec![])))
}
AuthType::Oauth2 => {
let name = "oauth2".to_string();
if let Some(oauth2) = &auth.oauth2 {
let mut flows: openapi3::Flows = Default::default();
let scopes = BTreeMap::from_iter(
oauth2
.scope
.clone()
.unwrap_or_default()
.iter()
.map(|s| self.resolve_variables(s, VAR_REPLACE_CREDITS))
.map(|s| (s.to_string(), s.to_string())),
);
let authorization_url = self.resolve_variables(
oauth2.auth_url.as_ref().unwrap_or(&"".to_string()),
VAR_REPLACE_CREDITS,
);
let token_url = self.resolve_variables(
oauth2.access_token_url.as_ref().unwrap_or(&"".to_string()),
VAR_REPLACE_CREDITS,
);
let refresh_url = oauth2
.refresh_token_url
.as_ref()
.map(|url| self.resolve_variables(url, VAR_REPLACE_CREDITS));
match oauth2.grant_type {
postman::Oauth2GrantType::AuthorizationCode
| postman::Oauth2GrantType::AuthorizationCodeWithPkce => {
flows.authorization_code = Some(openapi3::AuthorizationCodeFlow {
authorization_url,
token_url,
refresh_url,
scopes,
});
}
postman::Oauth2GrantType::ClientCredentials => {
flows.client_credentials = Some(openapi3::ClientCredentialsFlow {
token_url,
refresh_url,
scopes,
});
}
postman::Oauth2GrantType::PasswordCredentials => {
flows.password = Some(openapi3::PasswordFlow {
token_url,
refresh_url,
scopes,
});
}
postman::Oauth2GrantType::Implicit => {
flows.implicit = Some(openapi3::ImplicitFlow {
authorization_url,
refresh_url,
scopes,
});
}
}
let scheme = openapi3::SecurityScheme::OAuth2 {
flows: Box::new(flows),
};
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some(Some((name, oauth2.scope.clone().unwrap_or_default())))
} else {
let scheme = openapi3::SecurityScheme::OAuth2 {
flows: Default::default(),
};
security_schemes.insert(name.clone(), ObjectOrReference::Object(scheme));
Some(Some((name, vec![])))
}
}
_ => None,
};

security
}

fn extract_request_body(
&self,
body: &postman::Body,
Expand Down
10 changes: 7 additions & 3 deletions src/openapi/v3_0/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub struct Spec {
/// Only one of the security requirement objects need to be satisfied to authorize a request.
/// Individual operations can override this definition.
#[serde(skip_serializing_if = "Option::is_none")]
pub security: Option<SecurityRequirement>,
pub security: Option<Vec<SecurityRequirement>>,

/// A list of tags used by the specification with additional metadata.
///The order of the tags can be used to reflect on their order by the parsing tools.
Expand Down Expand Up @@ -335,7 +335,7 @@ pub struct Operation {
/// [`security`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#oasSecurity).
/// To remove a top-level security declaration, an empty array can be used.
#[serde(skip_serializing_if = "Option::is_none")]
pub security: Option<SecurityRequirement>,
pub security: Option<Vec<SecurityRequirement>>,

/// An alternative `server` array to service this operation. If an alternative `server`
/// object is specified at the Path Item Object or Root level, it will be overridden by
Expand Down Expand Up @@ -1004,7 +1004,11 @@ impl PartialEq for Tag {
}
impl Eq for Tag {}

type SecurityRequirement = BTreeMap<String, Vec<String>>;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct SecurityRequirement {
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub requirement: Option<BTreeMap<String, Vec<String>>>,
}

/// Allows referencing an external resource for extended documentation.
///
Expand Down
31 changes: 31 additions & 0 deletions tests/fixtures/noauth.postman.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"info": {
"_postman_id": "2a763711-fdbf-4c82-a7eb-f27c3db887e7",
"name": "OAuth Example",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"updatedAt": "2023-11-22T17:23:18.000Z",
"uid": "10354132-2a763711-fdbf-4c82-a7eb-f27c3db887e7"
},
"item": [],
"auth": {
"type": "noauth"
},
"event": [
{
"listen": "prerequest",
"script": {
"id": "bbaa0257-914e-4fc3-bb54-422263e7da80",
"type": "text/javascript",
"exec": [""]
}
},
{
"listen": "test",
"script": {
"id": "d5bbc401-c441-4db1-b80c-23e5fd06b8d3",
"type": "text/javascript",
"exec": [""]
}
}
]
}

0 comments on commit 6ee4e63

Please sign in to comment.