Skip to content

Commit 4b11531

Browse files
authored
Merge pull request #341 from umccr/fix/test-elsa-integration
fix: elsa integration
2 parents 6408498 + 222eb4e commit 4b11531

File tree

8 files changed

+90
-41
lines changed

8 files changed

+90
-41
lines changed

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

htsget-axum/src/handlers/get.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use super::{extract_request, handle_response};
12
use crate::server::AppState;
23
use axum::Extension;
34
use axum::extract::{Path, Query, State};
@@ -8,8 +9,6 @@ use http::HeaderMap;
89
use serde_json::Value;
910
use std::collections::HashMap;
1011

11-
use super::{extract_request, handle_response};
12-
1312
/// GET request reads endpoint.
1413
pub async fn reads<H: HtsGet + Send + Sync + 'static>(
1514
query: Query<HashMap<String, String>>,

htsget-config/README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -438,10 +438,6 @@ The following additional options can be configured under the `auth` table to ena
438438
| `forward_id` | Forwards the id of the request in a header called `Htsget-Context-Id`. The value of this header will be request path without the `/reads/` or `/variants/` component. For example, if a request path is `/reads/<id>`, this header will have the same value as `<id>`. | Boolean | `false` |
439439
| `passthrough_auth` | Forward the authorization header to the authorization server directly without renaming it to a `Htsget-Context-` custom header. If this is true, then the `Authorization` header is required with the request. | Boolean | `false` |
440440

441-
When using the `authorization_url`, the [authentication](#jwt-authentication) config must also be set as htsget-rs will
442-
forward the JWT token to the authorization server so that it can make decisions about the user's authorization. If the
443-
`authorization_url` is a file path, then authentication doesn't need to be set.
444-
445441
Each header in the `forward_headers` option is forwarded as a custom `Htsget-Context-<name>` header to the authorization server.
446442
The authorization header can be forward as though it is coming from the client by setting `forward_auth_header = true`. This is
447443
useful to support authenticating the original client JWT at the authorization server and can be used to set-up authorization

htsget-http/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ cfg-if = "1"
3030

3131
# JWT middleware
3232
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
33+
reqwest-middleware = "0.4"
3334
jsonwebtoken = "9"
3435
jsonpath-rust = "1"
3536
regex = "1"

htsget-http/src/error.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1+
use htsget_config::types::HtsGetError as HtsGetSearchError;
12
use http::StatusCode;
23
use http::header::{InvalidHeaderName, InvalidHeaderValue};
3-
use serde::Serialize;
4+
use serde::{Deserialize, Serialize};
45
use thiserror::Error;
56

6-
use htsget_config::types::HtsGetError as HtsGetSearchError;
7-
87
pub type Result<T> = core::result::Result<T, HtsGetError>;
98

109
/// An error type that describes the errors specified in the
@@ -29,18 +28,20 @@ pub enum HtsGetError {
2928
MethodNotAllowed(String),
3029
#[error("InternalError")]
3130
InternalError(String),
31+
#[error("Wrapped")]
32+
Wrapped(WrappedHtsGetError, StatusCode),
3233
}
3334

3435
/// A helper struct implementing [serde's Serialize trait](Serialize) to allow
3536
/// easily converting HtsGetErrors to JSON
36-
#[derive(Serialize)]
37+
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
3738
pub struct JsonHtsGetError {
3839
error: String,
3940
message: String,
4041
}
4142

4243
/// The "htsget" container wrapping the actual error response above
43-
#[derive(Serialize)]
44+
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
4445
pub struct WrappedHtsGetError {
4546
htsget: JsonHtsGetError,
4647
}
@@ -59,6 +60,7 @@ impl HtsGetError {
5960
| HtsGetError::InvalidRange(err) => (err, StatusCode::BAD_REQUEST),
6061
HtsGetError::MethodNotAllowed(err) => (err, StatusCode::METHOD_NOT_ALLOWED),
6162
HtsGetError::InternalError(err) => (err, StatusCode::INTERNAL_SERVER_ERROR),
63+
HtsGetError::Wrapped(err, status) => return (err.clone(), *status),
6264
};
6365

6466
(
@@ -97,3 +99,23 @@ impl From<InvalidHeaderValue> for HtsGetError {
9799
Self::InternalError(err.to_string())
98100
}
99101
}
102+
103+
impl From<reqwest_middleware::Error> for HtsGetError {
104+
fn from(err: reqwest_middleware::Error) -> Self {
105+
match err {
106+
reqwest_middleware::Error::Middleware(err) => HtsGetError::InternalError(err.to_string()),
107+
reqwest_middleware::Error::Reqwest(err) => err
108+
.status()
109+
.map(|status| match status {
110+
StatusCode::UNAUTHORIZED => HtsGetError::InvalidAuthentication(err.to_string()),
111+
StatusCode::FORBIDDEN => HtsGetError::PermissionDenied(err.to_string()),
112+
StatusCode::NOT_FOUND => HtsGetError::NotFound(err.to_string()),
113+
StatusCode::PAYLOAD_TOO_LARGE => HtsGetError::PayloadTooLarge(err.to_string()),
114+
StatusCode::BAD_REQUEST => HtsGetError::InvalidInput(err.to_string()),
115+
StatusCode::METHOD_NOT_ALLOWED => HtsGetError::MethodNotAllowed(err.to_string()),
116+
_ => HtsGetError::InternalError(err.to_string()),
117+
})
118+
.unwrap_or(HtsGetError::InternalError(err.to_string())),
119+
}
120+
}
121+
}

htsget-http/src/http_core.rs

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,46 +12,45 @@ use htsget_config::config::service_info::PackageInfo;
1212
use htsget_config::types::{JsonResponse, Query, Request, Response};
1313
use htsget_search::HtsGet;
1414
use http::HeaderMap;
15-
use jsonwebtoken::TokenData;
1615
use serde_json::Value;
1716
use tokio::select;
1817
use tracing::debug;
1918
use tracing::instrument;
2019

21-
async fn authenticate(
22-
headers: &HeaderMap,
23-
auth: Option<Auth>,
24-
) -> Result<Option<(TokenData<Value>, Auth)>> {
20+
async fn authenticate(headers: &HeaderMap, auth: Option<Auth>) -> Result<Option<Auth>> {
2521
if let Some(mut auth) = auth {
2622
if auth.config().auth_mode().is_some() {
27-
return Ok(Some((auth.validate_jwt(headers).await?, auth)));
23+
auth.validate_jwt(headers).await?;
24+
Ok(Some(auth))
25+
} else {
26+
Ok(Some(auth))
2827
}
28+
} else {
29+
Ok(auth)
2930
}
30-
31-
Ok(None)
3231
}
3332

3433
async fn authorize(
3534
headers: &HeaderMap,
3635
path: &str,
3736
queries: &mut [Query],
38-
auth: Option<(TokenData<Value>, Auth)>,
37+
auth: Option<Auth>,
3938
extensions: Option<Value>,
4039
endpoint: &Endpoint,
41-
) -> Result<Option<AuthorizationRestrictions>> {
42-
if let Some((_, mut auth)) = auth {
40+
) -> Result<Option<(AuthorizationRestrictions, bool)>> {
41+
if let Some(mut auth) = auth {
4342
let _rules = auth
4443
.validate_authorization(headers, path, queries, extensions, endpoint)
4544
.await?;
4645
cfg_if! {
4746
if #[cfg(feature = "experimental")] {
4847
if auth.config().add_hint() {
49-
Ok(_rules)
48+
Ok(_rules.map(|rules| (rules, true)))
5049
} else {
51-
Ok(None)
50+
Ok(_rules.map(|rules| (rules, false)))
5251
}
5352
} else {
54-
Ok(None)
53+
Ok(_rules.map(|rules| (rules, false)))
5554
}
5655
}
5756
} else {
@@ -75,6 +74,7 @@ pub async fn get(
7574
let headers = request.headers().clone();
7675

7776
let auth = authenticate(&headers, auth).await?;
77+
debug!(auth = ?auth, "auth");
7878

7979
let format = match_format_from_query(&endpoint, request.query())?;
8080
let mut query = vec![convert_to_query(request, format)?];
@@ -92,13 +92,15 @@ pub async fn get(
9292

9393
let query = query.into_iter().next().expect("single element vector");
9494

95-
let response = if let Some(ref rules) = rules {
95+
debug!(rules = ?rules, "rules");
96+
let response = if let Some((ref rules, _)) = rules {
9697
let mut remote_locations = rules.clone().into_remote_locations();
9798
if let Some(package_info) = package_info {
9899
remote_locations
99100
.set_from_package_info(package_info)
100101
.map_err(|_| InternalError("invalid remote locations".to_string()))?;
101102
}
103+
debug!(remote_locations = ?remote_locations, "remote locations");
102104

103105
// If there are remote locations, try them first.
104106
match remote_locations
@@ -115,7 +117,11 @@ pub async fn get(
115117

116118
cfg_if! {
117119
if #[cfg(feature = "experimental")] {
118-
Ok(response.with_allowed(rules.map(|r| r.into_rules())))
120+
let allowed = match rules {
121+
Some((rules, add_hint)) if add_hint => Some(rules.into_rules()),
122+
_ => None
123+
};
124+
Ok(response.with_allowed(allowed))
119125
} else {
120126
Ok(response)
121127
}
@@ -138,6 +144,7 @@ pub async fn post(
138144
let headers = request.headers().clone();
139145

140146
let auth = authenticate(&headers, auth).await?;
147+
debug!(auth = ?auth, "auth");
141148

142149
if !request.query().is_empty() {
143150
return Err(InvalidInput(
@@ -159,7 +166,9 @@ pub async fn post(
159166
debug!(endpoint = ?endpoint, queries = ?queries, "getting POST response");
160167

161168
let mut futures = FuturesOrdered::new();
162-
if let Some(ref rules) = rules {
169+
debug!(rules = ?rules, "rules");
170+
171+
if let Some((ref rules, _)) = rules {
163172
for query in queries {
164173
let mut remote_locations = rules.clone().into_remote_locations();
165174
if let Some(package_info) = package_info {
@@ -168,6 +177,7 @@ pub async fn post(
168177
.map_err(|_| InternalError("invalid remote locations".to_string()))?;
169178
}
170179
let owned_searcher = searcher.clone();
180+
debug!(remote_locations = ?remote_locations, "remote locations");
171181

172182
// If there are remote locations, try them first.
173183
futures.push_back(tokio::spawn(async move {
@@ -198,7 +208,11 @@ pub async fn post(
198208
JsonResponse::from(merge_responses(responses).expect("expected at least one response"));
199209
cfg_if! {
200210
if #[cfg(feature = "experimental")] {
201-
Ok(response.with_allowed(rules.map(|r| r.into_rules())))
211+
let allowed = match rules {
212+
Some((rules, add_hint)) if add_hint => Some(rules.into_rules()),
213+
_ => None
214+
};
215+
Ok(response.with_allowed(allowed))
202216
} else {
203217
Ok(response)
204218
}

htsget-http/src/middleware/auth.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! The htsget authorization middleware.
22
//!
33
4-
use crate::error::Result as HtsGetResult;
4+
use crate::error::{Result as HtsGetResult, WrappedHtsGetError};
55
use crate::middleware::error::Error::AuthBuilderError;
66
use crate::middleware::error::Result;
77
use crate::{Endpoint, HtsGetError};
@@ -22,7 +22,7 @@ use serde::de::DeserializeOwned;
2222
use serde_json::Value;
2323
use std::fmt::{Debug, Formatter};
2424
use std::str::FromStr;
25-
use tracing::trace;
25+
use tracing::{debug, trace};
2626

2727
/// The authorization middleware builder.
2828
#[derive(Default, Debug)]
@@ -88,22 +88,36 @@ impl Auth {
8888
headers: HeaderMap,
8989
) -> HtsGetResult<D> {
9090
trace!("fetching url: {}", url);
91-
let err = || HtsGetError::InternalError(format!("failed to fetch data from {url}"));
9291
let response = self
9392
.config
9493
.http_client()
95-
.map_err(|_| err())?
94+
.map_err(|err| HtsGetError::InternalError(format!("failed to fetch data from {url}: {err}")))?
9695
.get(url)
9796
.headers(headers)
9897
.send()
99-
.await
100-
.map_err(|_| err())?;
98+
.await?;
10199
trace!("response: {:?}", response);
102100

103-
response.json().await.map_err(|_| err())
101+
let status = response.status();
102+
103+
// Forward a valid htsget error if that's what the backend returns.
104+
let value = response.json::<Value>().await.map_err(|err| {
105+
HtsGetError::InternalError(format!("failed to fetch data from {url}: {err}"))
106+
})?;
107+
trace!("value: {}", value);
108+
109+
match serde_json::from_value::<D>(value.clone()) {
110+
Ok(response) => Ok(response),
111+
Err(_) => match serde_json::from_value::<WrappedHtsGetError>(value.clone()) {
112+
Ok(err) => Err(HtsGetError::Wrapped(err, status)),
113+
Err(_) => Err(HtsGetError::InternalError(format!(
114+
"failed to fetch data from {url}: {value}"
115+
))),
116+
},
117+
}
104118
}
105119

106-
/// Get a decoding key form the JWKS url.
120+
/// Get a decoding key from the JWKS url.
107121
pub async fn decode_jwks(&mut self, jwks_url: &Uri, token: &str) -> HtsGetResult<DecodingKey> {
108122
// Decode header and get the key id.
109123
let header = decode_header(token)?;
@@ -437,6 +451,8 @@ impl Auth {
437451
.query_authorization_service(headers, request_extensions, endpoint, path)
438452
.await?;
439453

454+
debug!(restrictions = ?restrictions, "restrictions");
455+
440456
if let Some(restrictions) = restrictions {
441457
cfg_if! {
442458
if #[cfg(feature = "experimental")] {

htsget-lambda/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ where
5959
let mut event: Request = lambda_request.into();
6060

6161
// After creating the request event, add the original request as an extension.
62+
debug!(original_request = ?original_request, "original_request");
6263
event.extensions_mut().insert(original_request);
6364

6465
let fut = Box::pin(self.service.call(event.with_lambda_context(req.context)));

0 commit comments

Comments
 (0)