Skip to content

Commit 99cbca0

Browse files
committed
feat: multiple securities
* Implement support for multiple securities * The map can pick between `first-valid` and `all` kinds, which affect whether all specified securities are applied or just the first valid
1 parent b6e02d5 commit 99cbca0

File tree

8 files changed

+102
-35
lines changed

8 files changed

+102
-35
lines changed

core/core/src/sf_core/map_std_impl/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use map_std::{
55
unstable::{
66
security::{resolve_security, SecurityMap},
77
HttpCallError as MapHttpCallError, HttpCallHeadError as MapHttpCallHeadError,
8-
HttpRequest as MapHttpRequest, HttpResponse as MapHttpResponse, MapStdUnstable, MapValue,
8+
HttpRequest as MapHttpRequest, HttpRequestSecurity as MapHttpRequestSecurity, HttpResponse as MapHttpResponse, MapStdUnstable, MapValue,
99
SetOutputError, TakeContextError,
1010
},
1111
MapStdFull,
@@ -90,9 +90,9 @@ impl MapStdUnstable for MapStdImpl {
9090
}
9191
}
9292

93-
fn http_call(&mut self, mut params: MapHttpRequest) -> Result<Handle, MapHttpCallError> {
93+
fn http_call(&mut self, mut params: MapHttpRequest, security: MapHttpRequestSecurity) -> Result<Handle, MapHttpCallError> {
9494
let security_map = self.security.as_ref().unwrap();
95-
resolve_security(security_map, &mut params)?;
95+
resolve_security(security_map, &mut params, &security)?;
9696

9797
// IDEA: add profile, provider info as well?
9898
params

core/core_to_map_std/src/unstable/mod.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ macro_rules! map_value {
182182
};
183183
}
184184

185+
pub enum HttpRequestSecurity {
186+
FirstValid(Vec<String>),
187+
All(Vec<String>),
188+
}
189+
185190
pub struct HttpRequest {
186191
/// HTTP method - will be used as-is.
187192
pub method: String,
@@ -192,9 +197,7 @@ pub struct HttpRequest {
192197
/// Multiple values with the same key will be repeated in the query string, no joining will be performed.
193198
pub query: MultiMap,
194199
/// Body as bytes.
195-
pub body: Option<Vec<u8>>,
196-
/// Security configuration
197-
pub security: Option<String>,
200+
pub body: Option<Vec<u8>>
198201
}
199202
pub struct HttpResponse {
200203
/// Status code of the response.
@@ -302,7 +305,7 @@ pub trait MapStdUnstable {
302305
fn stream_close(&mut self, handle: Handle) -> std::io::Result<()>;
303306

304307
// http
305-
fn http_call(&mut self, params: HttpRequest) -> Result<Handle, HttpCallError>;
308+
fn http_call(&mut self, params: HttpRequest, security: HttpRequestSecurity) -> Result<Handle, HttpCallError>;
306309
fn http_call_head(&mut self, handle: Handle) -> Result<HttpResponse, HttpCallHeadError>;
307310

308311
// input and output
@@ -315,6 +318,16 @@ pub trait MapStdUnstable {
315318
// MESSAGES //
316319
//////////////
317320

321+
#[derive(Deserialize)]
322+
#[serde(tag = "kind", content = "ids")]
323+
#[serde(rename_all = "kebab-case")]
324+
enum HttpRequestSecuritySettingMessage {
325+
FirstValid(Vec<String>),
326+
All(Vec<String>),
327+
#[serde(untagged)]
328+
Legacy(Option<String>)
329+
}
330+
318331
define_exchange_map_to_core! {
319332
let state: MapStdUnstable;
320333
enum RequestUnstable {
@@ -324,7 +337,7 @@ define_exchange_map_to_core! {
324337
url: String,
325338
headers: HeadersMultiMap,
326339
query: MultiMap,
327-
security: Option<String>,
340+
security: HttpRequestSecuritySettingMessage,
328341
body: Option<Vec<u8>>,
329342
} -> enum Response {
330343
Ok {
@@ -341,8 +354,12 @@ define_exchange_map_to_core! {
341354
url,
342355
headers,
343356
query,
344-
security,
345357
body,
358+
}, match security {
359+
HttpRequestSecuritySettingMessage::FirstValid(v) => HttpRequestSecurity::FirstValid(v),
360+
HttpRequestSecuritySettingMessage::All(v) => HttpRequestSecurity::All(v),
361+
// Turns Option<String> into Vec<String>, the vec being empty on None
362+
HttpRequestSecuritySettingMessage::Legacy(maybe_v) => HttpRequestSecurity::FirstValid(maybe_v.into_iter().collect()),
346363
});
347364

348365
match handle {

core/core_to_map_std/src/unstable/security.rs

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use sf_std::{
1111
HeaderName,
1212
};
1313

14-
use super::{HttpCallError, HttpRequest, MapValue, MapValueObject};
14+
use super::{HttpCallError, HttpRequest, HttpRequestSecurity, MapValue, MapValueObject};
1515

1616
pub enum ApiKeyPlacement {
1717
Header,
@@ -319,25 +319,65 @@ pub fn prepare_security_map(
319319
pub fn resolve_security(
320320
security_map: &SecurityMap,
321321
params: &mut HttpRequest,
322+
security: &HttpRequestSecurity
322323
) -> Result<(), HttpCallError> {
323-
let security = match params.security {
324-
None => return Ok(()),
325-
Some(ref security) => security,
326-
};
324+
match security {
325+
HttpRequestSecurity::FirstValid(ref ids) => {
326+
let mut first_error = None;
327+
for id in ids {
328+
match try_resolve_security(security_map, params, id) {
329+
Ok(()) => return Ok(()),
330+
Err(err) => {
331+
if first_error.is_none() {
332+
first_error = Some(err);
333+
}
334+
}
335+
}
336+
}
337+
338+
match first_error {
339+
None => Ok(()),
340+
Some(err) => return Err(HttpCallError::InvalidSecurityConfiguration(
341+
err
342+
))
343+
}
344+
}
345+
HttpRequestSecurity::All(ref ids) => {
346+
let mut all_errors = Vec::new();
347+
for id in ids {
348+
match try_resolve_security(security_map, params, id) {
349+
Ok(()) => (),
350+
Err(err) => all_errors.push(err.to_string())
351+
}
352+
}
327353

328-
let security_config = security_map.get(security.as_str());
354+
if all_errors.len() > 0 {
355+
return Err(HttpCallError::InvalidSecurityConfiguration(
356+
all_errors.join("\n")
357+
))
358+
}
359+
Ok(())
360+
}
361+
}
362+
}
363+
fn try_resolve_security(
364+
security_map: &SecurityMap,
365+
params: &mut HttpRequest,
366+
security: &str
367+
) -> Result<(), String> {
368+
let security_config = security_map.get(security);
329369

330370
match security_config {
331371
None => {
332-
return Err(HttpCallError::InvalidSecurityConfiguration(format!(
372+
return Err(format!(
333373
"Security configuration for {} is missing",
334374
security
335-
)));
375+
));
336376
}
337377
Some(SecurityMapValue::Error(err)) => {
338-
return Err(HttpCallError::InvalidSecurityConfiguration(
378+
return Err(
339379
SecurityMisconfiguredError::format_errors(std::slice::from_ref(err)),
340-
));
380+
);
341381
}
342382
Some(SecurityMapValue::Security(Security::Http(HttpSecurity::Basic {
343383
username,
@@ -384,10 +424,10 @@ pub fn resolve_security(
384424
if let Some(body) = &params.body {
385425
let mut body =
386426
serde_json::from_slice::<serde_json::Value>(body).map_err(|e| {
387-
HttpCallError::InvalidSecurityConfiguration(format!(
427+
format!(
388428
"Failed to parse body: {}",
389429
e
390-
))
430+
)
391431
})?;
392432

393433
let keys = if name.starts_with('/') {
@@ -397,50 +437,50 @@ pub fn resolve_security(
397437
};
398438

399439
if keys.is_empty() {
400-
return Err(HttpCallError::InvalidSecurityConfiguration(format!(
440+
return Err(format!(
401441
"Invalid field name '{}'",
402442
name
403-
)));
443+
));
404444
}
405445

406446
let mut nested = &mut body;
407447
for (key_idx, key) in keys.iter().enumerate() {
408448
if nested.is_array() {
409449
let key_number = match key.parse::<usize>() {
410450
Ok(n) => n,
411-
Err(_) => return Err(HttpCallError::InvalidSecurityConfiguration(format!(
451+
Err(_) => return Err(format!(
412452
"Field value on path '/{}' is an array but provided key cannot be parsed as a number",
413453
&keys[0..=key_idx].join("/")
414-
)))
454+
))
415455
};
416456
nested = &mut nested[key_number];
417457
} else if nested.is_object() {
418458
nested = &mut nested[key];
419459
} else {
420-
return Err(HttpCallError::InvalidSecurityConfiguration(format!(
460+
return Err(format!(
421461
"Field value on path '/{}' must be an object or an array",
422462
&keys[0..=key_idx].join("/")
423-
)));
463+
));
424464
}
425465
}
426466
*nested = serde_json::Value::from(apikey.to_string());
427467

428468
params.body = Some(serde_json::to_vec(&body).map_err(|e| {
429-
HttpCallError::InvalidSecurityConfiguration(format!(
469+
format!(
430470
"Failed to serialize body: {}",
431471
e
432-
))
472+
)
433473
})?);
434474
} else {
435-
return Err(HttpCallError::InvalidSecurityConfiguration(
475+
return Err(
436476
"Api key placement is set to body but the body is empty".to_string(),
437-
));
477+
);
438478
}
439479
}
440480
(ApiKeyPlacement::Body, None) => {
441-
return Err(HttpCallError::InvalidSecurityConfiguration(
481+
return Err(
442482
"Missing body type".to_string(),
443-
));
483+
);
444484
}
445485
},
446486
}

core/interpreter_js/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ mod test {
139139
fn http_call(
140140
&mut self,
141141
_params: map_std::unstable::HttpRequest,
142+
_security: map_std::unstable::HttpRequestSecurity
142143
) -> Result<sf_std::abi::Handle, map_std::unstable::HttpCallError> {
143144
Ok(1)
144145
}

core_js/map-std/src/unstable.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export type FetchOptions = {
1111
headers?: MultiMap,
1212
query?: MultiMap,
1313
body?: AnyValue,
14-
security?: string,
14+
/** Security configs to apply to this request. Specifying a string is equal to using `first-valid` */
15+
security?: string | { kind: 'first-valid', ids: string[] } | { kind: 'all', ids: string[] },
1516
};
1617

1718
// Can't use Record<string, AnyValue> but can use { [s in string]: AnyValue }. Typescript go brr.

examples/comlinks/src/localhost.provider.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
"id": "basic_auth",
1919
"type": "http",
2020
"scheme": "basic"
21+
},
22+
{
23+
"id": "authic_base",
24+
"type": "apiKey",
25+
"in": "header",
26+
"name": "X-API-KEY"
2127
}
2228
]
2329
}

examples/comlinks/src/wasm-sdk.example.localhost.map.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ var Example = ({ input, parameters, services }) => {
2626
'x-custom-header': [parameters.PARAM]
2727
},
2828
security: 'basic_auth',
29+
// security: { kind: "first-valid", ids: ['authic_base', 'basic_auth'] },
30+
// security: { kind: "all", ids: ['authic_base', 'basic_auth'] },
2931
query: {
3032
'foo': ['bar', 'baz'],
3133
'qux': ['2']

examples/python/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def do_GET(self):
3636
{ "id": 1 },
3737
provider = "localhost",
3838
parameters = { "PARAM": "parameter_value" },
39-
security = { "basic_auth": { "username": "username", "password": "password" } }
39+
security = { "basic_auth": { "username": "username", "password": "password" }, "authic_base": { "apikey": "api_key_value" } }
4040
)
4141
print(f"RESULT: {r}")
4242
except PerformError as e:

0 commit comments

Comments
 (0)