itsi-scheduler 0.2.22-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +8 -0
- data/Cargo.lock +997 -0
- data/Cargo.toml +7 -0
- data/Rakefile +39 -0
- data/ext/itsi_acme/Cargo.toml +86 -0
- data/ext/itsi_acme/examples/high_level.rs +63 -0
- data/ext/itsi_acme/examples/high_level_warp.rs +52 -0
- data/ext/itsi_acme/examples/low_level.rs +87 -0
- data/ext/itsi_acme/examples/low_level_axum.rs +66 -0
- data/ext/itsi_acme/src/acceptor.rs +81 -0
- data/ext/itsi_acme/src/acme.rs +354 -0
- data/ext/itsi_acme/src/axum.rs +86 -0
- data/ext/itsi_acme/src/cache.rs +39 -0
- data/ext/itsi_acme/src/caches/boxed.rs +80 -0
- data/ext/itsi_acme/src/caches/composite.rs +69 -0
- data/ext/itsi_acme/src/caches/dir.rs +106 -0
- data/ext/itsi_acme/src/caches/mod.rs +11 -0
- data/ext/itsi_acme/src/caches/no.rs +78 -0
- data/ext/itsi_acme/src/caches/test.rs +136 -0
- data/ext/itsi_acme/src/config.rs +172 -0
- data/ext/itsi_acme/src/https_helper.rs +69 -0
- data/ext/itsi_acme/src/incoming.rs +142 -0
- data/ext/itsi_acme/src/jose.rs +161 -0
- data/ext/itsi_acme/src/lib.rs +142 -0
- data/ext/itsi_acme/src/resolver.rs +59 -0
- data/ext/itsi_acme/src/state.rs +424 -0
- data/ext/itsi_error/Cargo.lock +368 -0
- data/ext/itsi_error/Cargo.toml +12 -0
- data/ext/itsi_error/src/lib.rs +140 -0
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.lock +355 -0
- data/ext/itsi_rb_helpers/Cargo.toml +11 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +232 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/extconf.rb +11 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +320 -0
- data/ext/itsi_scheduler/src/lib.rs +39 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +94 -0
- data/ext/itsi_server/src/default_responses/mod.rs +14 -0
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +154 -0
- data/ext/itsi_server/src/prelude.rs +2 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +116 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +149 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +346 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +265 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +399 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +447 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +545 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +650 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +102 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
- data/ext/itsi_server/src/server/binds/bind.rs +204 -0
- data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/binds/listener.rs +485 -0
- data/ext/itsi_server/src/server/binds/mod.rs +4 -0
- data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/binds/tls.rs +278 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/frame_stream.rs +143 -0
- data/ext/itsi_server/src/server/http_message_types.rs +230 -0
- data/ext/itsi_server/src/server/io_stream.rs +128 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +170 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +63 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +94 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +93 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +343 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +151 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +329 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +300 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +193 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +64 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +188 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +168 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +183 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +209 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +133 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +122 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +407 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +155 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +54 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +54 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +51 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +138 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +269 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +62 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +218 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +31 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +381 -0
- data/ext/itsi_server/src/server/mod.rs +14 -0
- data/ext/itsi_server/src/server/process_worker.rs +247 -0
- data/ext/itsi_server/src/server/redirect_type.rs +26 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +100 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +411 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +31 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +449 -0
- data/ext/itsi_server/src/server/signal.rs +129 -0
- data/ext/itsi_server/src/server/size_limited_incoming.rs +107 -0
- data/ext/itsi_server/src/server/thread_worker.rs +504 -0
- data/ext/itsi_server/src/services/cache_store.rs +74 -0
- data/ext/itsi_server/src/services/itsi_http_service.rs +270 -0
- data/ext/itsi_server/src/services/mime_types.rs +2896 -0
- data/ext/itsi_server/src/services/mod.rs +6 -0
- data/ext/itsi_server/src/services/password_hasher.rs +89 -0
- data/ext/itsi_server/src/services/rate_limiter.rs +609 -0
- data/ext/itsi_server/src/services/static_file_server.rs +1400 -0
- data/ext/itsi_tracing/Cargo.lock +274 -0
- data/ext/itsi_tracing/Cargo.toml +17 -0
- data/ext/itsi_tracing/src/lib.rs +370 -0
- data/itsi-scheduler-100.png +0 -0
- data/lib/itsi/schedule_refinement.rb +96 -0
- data/lib/itsi/scheduler/3.1/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.2/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.3/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/3.4/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/4.0/itsi_scheduler.so +0 -0
- data/lib/itsi/scheduler/native_extension.rb +34 -0
- data/lib/itsi/scheduler/version.rb +7 -0
- data/lib/itsi/scheduler.rb +153 -0
- data/vendor/rb-sys-build/.cargo-ok +1 -0
- data/vendor/rb-sys-build/.cargo_vcs_info.json +6 -0
- data/vendor/rb-sys-build/Cargo.lock +294 -0
- data/vendor/rb-sys-build/Cargo.toml +71 -0
- data/vendor/rb-sys-build/Cargo.toml.orig +32 -0
- data/vendor/rb-sys-build/LICENSE-APACHE +190 -0
- data/vendor/rb-sys-build/LICENSE-MIT +21 -0
- data/vendor/rb-sys-build/src/bindings/sanitizer.rs +185 -0
- data/vendor/rb-sys-build/src/bindings/stable_api.rs +247 -0
- data/vendor/rb-sys-build/src/bindings/wrapper.h +71 -0
- data/vendor/rb-sys-build/src/bindings.rs +280 -0
- data/vendor/rb-sys-build/src/cc.rs +421 -0
- data/vendor/rb-sys-build/src/lib.rs +12 -0
- data/vendor/rb-sys-build/src/rb_config/flags.rs +101 -0
- data/vendor/rb-sys-build/src/rb_config/library.rs +132 -0
- data/vendor/rb-sys-build/src/rb_config/search_path.rs +57 -0
- data/vendor/rb-sys-build/src/rb_config.rs +906 -0
- data/vendor/rb-sys-build/src/utils.rs +53 -0
- metadata +210 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
use super::{error_response::ErrorResponse, token_source::TokenSource, FromValue, MiddlewareLayer};
|
|
2
|
+
use crate::{
|
|
3
|
+
server::http_message_types::{HttpRequest, HttpResponse, RequestExt},
|
|
4
|
+
services::itsi_http_service::HttpRequestContext,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
use async_trait::async_trait;
|
|
8
|
+
use base64::{engine::general_purpose, Engine};
|
|
9
|
+
use derive_more::Debug;
|
|
10
|
+
use either::Either;
|
|
11
|
+
use itsi_error::ItsiError;
|
|
12
|
+
use jsonwebtoken::{
|
|
13
|
+
decode, decode_header, Algorithm as JwtAlg, DecodingKey, TokenData, Validation,
|
|
14
|
+
};
|
|
15
|
+
use magnus::error::Result;
|
|
16
|
+
use serde::Deserialize;
|
|
17
|
+
use std::{
|
|
18
|
+
collections::{HashMap, HashSet},
|
|
19
|
+
sync::OnceLock,
|
|
20
|
+
};
|
|
21
|
+
use tracing::debug;
|
|
22
|
+
|
|
23
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
24
|
+
pub struct AuthJwt {
|
|
25
|
+
pub token_source: TokenSource,
|
|
26
|
+
// The verifiers map still holds base64-encoded key strings keyed by algorithm.
|
|
27
|
+
pub verifiers: HashMap<JwtAlgorithm, Vec<String>>,
|
|
28
|
+
// We now store jsonwebtoken’s DecodingKey in our OnceLock.
|
|
29
|
+
#[serde(skip_deserializing)]
|
|
30
|
+
#[debug(skip)]
|
|
31
|
+
pub keys: OnceLock<HashMap<JwtAlgorithm, Vec<DecodingKey>>>,
|
|
32
|
+
pub audiences: Option<HashSet<String>>,
|
|
33
|
+
pub subjects: Option<HashSet<String>>,
|
|
34
|
+
pub issuers: Option<HashSet<String>>,
|
|
35
|
+
#[serde(skip_deserializing)]
|
|
36
|
+
pub audience_vec: OnceLock<Option<Vec<String>>>,
|
|
37
|
+
#[serde(skip_deserializing)]
|
|
38
|
+
pub issuer_vec: OnceLock<Option<Vec<String>>>,
|
|
39
|
+
pub leeway: Option<u64>,
|
|
40
|
+
#[serde(default = "unauthorized_error_response")]
|
|
41
|
+
pub error_response: ErrorResponse,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn unauthorized_error_response() -> ErrorResponse {
|
|
45
|
+
ErrorResponse::unauthorized()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Hash)]
|
|
49
|
+
pub enum JwtAlgorithm {
|
|
50
|
+
#[serde(rename(deserialize = "hs256"))]
|
|
51
|
+
Hs256,
|
|
52
|
+
#[serde(rename(deserialize = "hs384"))]
|
|
53
|
+
Hs384,
|
|
54
|
+
#[serde(rename(deserialize = "hs512"))]
|
|
55
|
+
Hs512,
|
|
56
|
+
#[serde(rename(deserialize = "rs256"))]
|
|
57
|
+
Rs256,
|
|
58
|
+
#[serde(rename(deserialize = "rs384"))]
|
|
59
|
+
Rs384,
|
|
60
|
+
#[serde(rename(deserialize = "rs512"))]
|
|
61
|
+
Rs512,
|
|
62
|
+
#[serde(rename(deserialize = "es256"))]
|
|
63
|
+
Es256,
|
|
64
|
+
#[serde(rename(deserialize = "es384"))]
|
|
65
|
+
Es384,
|
|
66
|
+
#[serde(rename(deserialize = "ps256"))]
|
|
67
|
+
Ps256,
|
|
68
|
+
#[serde(rename(deserialize = "ps384"))]
|
|
69
|
+
Ps384,
|
|
70
|
+
#[serde(rename(deserialize = "ps512"))]
|
|
71
|
+
Ps512,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Allow conversion from jsonwebtoken’s Algorithm to our JwtAlgorithm.
|
|
75
|
+
impl From<JwtAlg> for JwtAlgorithm {
|
|
76
|
+
fn from(alg: JwtAlg) -> Self {
|
|
77
|
+
match alg {
|
|
78
|
+
JwtAlg::HS256 => JwtAlgorithm::Hs256,
|
|
79
|
+
JwtAlg::HS384 => JwtAlgorithm::Hs384,
|
|
80
|
+
JwtAlg::HS512 => JwtAlgorithm::Hs512,
|
|
81
|
+
JwtAlg::RS256 => JwtAlgorithm::Rs256,
|
|
82
|
+
JwtAlg::RS384 => JwtAlgorithm::Rs384,
|
|
83
|
+
JwtAlg::RS512 => JwtAlgorithm::Rs512,
|
|
84
|
+
JwtAlg::ES256 => JwtAlgorithm::Es256,
|
|
85
|
+
JwtAlg::ES384 => JwtAlgorithm::Es384,
|
|
86
|
+
JwtAlg::PS256 => JwtAlgorithm::Ps256,
|
|
87
|
+
JwtAlg::PS384 => JwtAlgorithm::Ps384,
|
|
88
|
+
JwtAlg::PS512 => JwtAlgorithm::Ps512,
|
|
89
|
+
_ => panic!("Unsupported algorithm"),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
impl JwtAlgorithm {
|
|
95
|
+
/// Given a base64-encoded key string, decode and construct a jsonwebtoken::DecodingKey.
|
|
96
|
+
pub fn key_from(&self, base64: &str) -> itsi_error::Result<DecodingKey> {
|
|
97
|
+
match self {
|
|
98
|
+
// For HMAC algorithms, expect a base64 encoded secret.
|
|
99
|
+
JwtAlgorithm::Hs256 | JwtAlgorithm::Hs384 | JwtAlgorithm::Hs512 => {
|
|
100
|
+
Ok(DecodingKey::from_secret(
|
|
101
|
+
&general_purpose::STANDARD
|
|
102
|
+
.decode(base64)
|
|
103
|
+
.map_err(ItsiError::new)?,
|
|
104
|
+
))
|
|
105
|
+
}
|
|
106
|
+
// For RSA (and PS) algorithms, expect a PEM-formatted key.
|
|
107
|
+
JwtAlgorithm::Rs256
|
|
108
|
+
| JwtAlgorithm::Rs384
|
|
109
|
+
| JwtAlgorithm::Rs512
|
|
110
|
+
| JwtAlgorithm::Ps256
|
|
111
|
+
| JwtAlgorithm::Ps384
|
|
112
|
+
| JwtAlgorithm::Ps512 => DecodingKey::from_rsa_pem(base64.trim_ascii().as_bytes())
|
|
113
|
+
.map_err(|e| ItsiError::new(e.to_string())),
|
|
114
|
+
// For ECDSA algorithms, expect a PEM-formatted key.
|
|
115
|
+
JwtAlgorithm::Es256 | JwtAlgorithm::Es384 => {
|
|
116
|
+
DecodingKey::from_ec_pem(base64.trim_ascii().as_bytes())
|
|
117
|
+
.map_err(|e| ItsiError::new(e.to_string()))
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#[derive(Debug, Deserialize)]
|
|
124
|
+
#[serde(untagged)]
|
|
125
|
+
#[allow(dead_code)]
|
|
126
|
+
enum Audience {
|
|
127
|
+
Single(String),
|
|
128
|
+
Multiple(Vec<String>),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#[derive(Debug, Deserialize)]
|
|
132
|
+
#[allow(dead_code)]
|
|
133
|
+
struct Claims {
|
|
134
|
+
// Here we assume the token includes an expiration.
|
|
135
|
+
exp: usize,
|
|
136
|
+
// The audience claim may be a single string or an array.
|
|
137
|
+
aud: Option<Audience>,
|
|
138
|
+
sub: Option<String>,
|
|
139
|
+
iss: Option<String>,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#[async_trait]
|
|
143
|
+
impl MiddlewareLayer for AuthJwt {
|
|
144
|
+
async fn initialize(&self) -> Result<()> {
|
|
145
|
+
debug!(
|
|
146
|
+
target: "middleware::auth_jwt",
|
|
147
|
+
"Instantiating auth_jwt with {} verifiers", self.verifiers.len()
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
let keys: HashMap<JwtAlgorithm, Vec<DecodingKey>> = self
|
|
151
|
+
.verifiers
|
|
152
|
+
.iter()
|
|
153
|
+
.map(|(algorithm, key_strings)| {
|
|
154
|
+
let algo = algorithm.clone();
|
|
155
|
+
let keys: itsi_error::Result<Vec<DecodingKey>> = key_strings
|
|
156
|
+
.iter()
|
|
157
|
+
.map(|key_string| algorithm.key_from(key_string))
|
|
158
|
+
.inspect(|key_result| {
|
|
159
|
+
if key_result.is_err() {
|
|
160
|
+
debug!(
|
|
161
|
+
target: "middleware::auth_jwt",
|
|
162
|
+
"Failed to load key for algorithm {:?}", algorithm
|
|
163
|
+
)
|
|
164
|
+
} else {
|
|
165
|
+
debug!(
|
|
166
|
+
target: "middleware::auth_jwt",
|
|
167
|
+
"Loaded key for algorithm {:?}", algorithm
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
.collect();
|
|
172
|
+
keys.map(|keys| (algo, keys))
|
|
173
|
+
})
|
|
174
|
+
.collect::<itsi_error::Result<HashMap<JwtAlgorithm, Vec<DecodingKey>>>>()?;
|
|
175
|
+
|
|
176
|
+
self.keys
|
|
177
|
+
.set(keys)
|
|
178
|
+
.map_err(|_| ItsiError::new("Failed to set keys"))?;
|
|
179
|
+
|
|
180
|
+
if let Some(audiences) = self.audiences.as_ref() {
|
|
181
|
+
self.audience_vec
|
|
182
|
+
.set(Some(audiences.iter().cloned().collect::<Vec<_>>()))
|
|
183
|
+
.ok();
|
|
184
|
+
}
|
|
185
|
+
if let Some(issuers) = self.issuers.as_ref() {
|
|
186
|
+
self.issuer_vec
|
|
187
|
+
.set(Some(issuers.iter().cloned().collect::<Vec<_>>()))
|
|
188
|
+
.ok();
|
|
189
|
+
}
|
|
190
|
+
Ok(())
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async fn before(
|
|
194
|
+
&self,
|
|
195
|
+
req: HttpRequest,
|
|
196
|
+
_: &mut HttpRequestContext,
|
|
197
|
+
) -> Result<Either<HttpRequest, HttpResponse>> {
|
|
198
|
+
// Retrieve the JWT token from either a header or a query parameter.
|
|
199
|
+
let token_str = match &self.token_source {
|
|
200
|
+
TokenSource::Header { name, prefix } => {
|
|
201
|
+
debug!(
|
|
202
|
+
target: "middleware::auth_jwt",
|
|
203
|
+
"Extracting JWT from header: {}, prefix: {:?}",
|
|
204
|
+
name, prefix
|
|
205
|
+
);
|
|
206
|
+
if let Some(header) = req.header(name) {
|
|
207
|
+
if let Some(prefix) = prefix {
|
|
208
|
+
Some(header.strip_prefix(prefix).unwrap_or("").trim_ascii())
|
|
209
|
+
} else {
|
|
210
|
+
Some(header.trim_ascii())
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
None
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
TokenSource::Query(query_name) => {
|
|
217
|
+
debug!(
|
|
218
|
+
target: "middleware::auth_jwt",
|
|
219
|
+
"Extracting JWT from query parameter: {}",
|
|
220
|
+
query_name
|
|
221
|
+
);
|
|
222
|
+
req.query_param(query_name)
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
if token_str.is_none() {
|
|
227
|
+
debug!(
|
|
228
|
+
target: "middleware::auth_jwt",
|
|
229
|
+
"No JWT found in headers or query parameters"
|
|
230
|
+
);
|
|
231
|
+
return Ok(Either::Right(
|
|
232
|
+
self.error_response
|
|
233
|
+
.to_http_response(req.accept().into())
|
|
234
|
+
.await,
|
|
235
|
+
));
|
|
236
|
+
}
|
|
237
|
+
let token_str = token_str.unwrap();
|
|
238
|
+
let header = match decode_header(token_str) {
|
|
239
|
+
Ok(header) => header,
|
|
240
|
+
Err(_) => {
|
|
241
|
+
debug!(target: "middleware::auth_jwt", "JWT decoding failed");
|
|
242
|
+
return Ok(Either::Right(
|
|
243
|
+
self.error_response
|
|
244
|
+
.to_http_response(req.accept().into())
|
|
245
|
+
.await,
|
|
246
|
+
));
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
let alg: JwtAlgorithm = header.alg.into();
|
|
251
|
+
|
|
252
|
+
debug!(
|
|
253
|
+
target: "middleware::auth_jwt",
|
|
254
|
+
"Matched algorithm {:?}", alg
|
|
255
|
+
);
|
|
256
|
+
if !self.verifiers.contains_key(&alg) {
|
|
257
|
+
return Ok(Either::Right(
|
|
258
|
+
self.error_response
|
|
259
|
+
.to_http_response(req.accept().into())
|
|
260
|
+
.await,
|
|
261
|
+
));
|
|
262
|
+
}
|
|
263
|
+
let keys = self.keys.get().unwrap().get(&alg).unwrap();
|
|
264
|
+
|
|
265
|
+
// Build validation based on the algorithm and optional leeway.
|
|
266
|
+
let mut validation = Validation::new(match alg {
|
|
267
|
+
JwtAlgorithm::Hs256 => JwtAlg::HS256,
|
|
268
|
+
JwtAlgorithm::Hs384 => JwtAlg::HS384,
|
|
269
|
+
JwtAlgorithm::Hs512 => JwtAlg::HS512,
|
|
270
|
+
JwtAlgorithm::Rs256 => JwtAlg::RS256,
|
|
271
|
+
JwtAlgorithm::Rs384 => JwtAlg::RS384,
|
|
272
|
+
JwtAlgorithm::Rs512 => JwtAlg::RS512,
|
|
273
|
+
JwtAlgorithm::Es256 => JwtAlg::ES256,
|
|
274
|
+
JwtAlgorithm::Es384 => JwtAlg::ES384,
|
|
275
|
+
JwtAlgorithm::Ps256 => JwtAlg::PS256,
|
|
276
|
+
JwtAlgorithm::Ps384 => JwtAlg::PS384,
|
|
277
|
+
JwtAlgorithm::Ps512 => JwtAlg::PS512,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if let Some(leeway) = self.leeway {
|
|
281
|
+
validation.leeway = leeway;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if let Some(Some(auds)) = &self.audience_vec.get() {
|
|
285
|
+
validation.set_audience(auds);
|
|
286
|
+
validation.required_spec_claims.insert("aud".to_owned());
|
|
287
|
+
} else {
|
|
288
|
+
validation.validate_aud = false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if let Some(Some(issuers)) = &self.issuer_vec.get() {
|
|
292
|
+
validation.set_issuer(issuers);
|
|
293
|
+
validation.required_spec_claims.insert("iss".to_owned());
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if self.subjects.is_some() {
|
|
297
|
+
validation.required_spec_claims.insert("sub".to_owned());
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let token_data: Option<TokenData<Claims>> =
|
|
301
|
+
keys.iter()
|
|
302
|
+
.find_map(|key| match decode::<Claims>(token_str, key, &validation) {
|
|
303
|
+
Ok(data) => Some(data),
|
|
304
|
+
Err(e) => {
|
|
305
|
+
debug!("Token validation failed: {:?}", e);
|
|
306
|
+
None
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
let token_data = if let Some(data) = token_data {
|
|
311
|
+
data
|
|
312
|
+
} else {
|
|
313
|
+
return Ok(Either::Right(
|
|
314
|
+
self.error_response
|
|
315
|
+
.to_http_response(req.accept().into())
|
|
316
|
+
.await,
|
|
317
|
+
));
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
let claims = token_data.claims;
|
|
321
|
+
|
|
322
|
+
if let Some(expected_subjects) = &self.subjects {
|
|
323
|
+
if let Some(sub) = &claims.sub {
|
|
324
|
+
if !expected_subjects.contains(sub) {
|
|
325
|
+
debug!(
|
|
326
|
+
target: "middleware::auth_jwt",
|
|
327
|
+
"SUB check failed, token_sub: {:?}, expected_subjects: {:?}",
|
|
328
|
+
sub, expected_subjects
|
|
329
|
+
);
|
|
330
|
+
return Ok(Either::Right(
|
|
331
|
+
self.error_response
|
|
332
|
+
.to_http_response(req.accept().into())
|
|
333
|
+
.await,
|
|
334
|
+
));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
Ok(Either::Left(req))
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
impl FromValue for AuthJwt {}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
use crate::{
|
|
2
|
+
server::http_message_types::HttpResponse, services::itsi_http_service::HttpRequestContext,
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
use super::{FromValue, MiddlewareLayer};
|
|
6
|
+
use async_trait::async_trait;
|
|
7
|
+
use http::{HeaderName, HeaderValue};
|
|
8
|
+
use magnus::error::Result;
|
|
9
|
+
use serde::Deserialize;
|
|
10
|
+
use std::{collections::HashMap, sync::OnceLock};
|
|
11
|
+
use tracing::debug;
|
|
12
|
+
|
|
13
|
+
#[derive(Debug, Deserialize)]
|
|
14
|
+
pub struct CacheControl {
|
|
15
|
+
#[serde(default)]
|
|
16
|
+
pub max_age: Option<u64>,
|
|
17
|
+
#[serde(default)]
|
|
18
|
+
pub s_max_age: Option<u64>,
|
|
19
|
+
#[serde(default)]
|
|
20
|
+
pub stale_while_revalidate: Option<u64>,
|
|
21
|
+
#[serde(default)]
|
|
22
|
+
pub stale_if_error: Option<u64>,
|
|
23
|
+
#[serde(default)]
|
|
24
|
+
pub public: bool,
|
|
25
|
+
#[serde(default)]
|
|
26
|
+
pub private: bool,
|
|
27
|
+
#[serde(default)]
|
|
28
|
+
pub no_cache: bool,
|
|
29
|
+
#[serde(default)]
|
|
30
|
+
pub no_store: bool,
|
|
31
|
+
#[serde(default)]
|
|
32
|
+
pub must_revalidate: bool,
|
|
33
|
+
#[serde(default)]
|
|
34
|
+
pub proxy_revalidate: bool,
|
|
35
|
+
#[serde(default)]
|
|
36
|
+
pub immutable: bool,
|
|
37
|
+
#[serde(default)]
|
|
38
|
+
pub vary: Vec<String>,
|
|
39
|
+
#[serde(default)]
|
|
40
|
+
pub additional_headers: HashMap<String, String>,
|
|
41
|
+
#[serde(skip_deserializing)]
|
|
42
|
+
pub cache_control_str: OnceLock<String>,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[async_trait]
|
|
46
|
+
impl MiddlewareLayer for CacheControl {
|
|
47
|
+
async fn initialize(&self) -> Result<()> {
|
|
48
|
+
let mut directives = Vec::new();
|
|
49
|
+
|
|
50
|
+
if self.public && !self.private {
|
|
51
|
+
directives.push("public".to_owned());
|
|
52
|
+
} else if self.private && !self.public {
|
|
53
|
+
directives.push("private".to_owned());
|
|
54
|
+
}
|
|
55
|
+
if self.no_cache {
|
|
56
|
+
directives.push("no-cache".to_owned());
|
|
57
|
+
}
|
|
58
|
+
if self.no_store {
|
|
59
|
+
directives.push("no-store".to_owned());
|
|
60
|
+
}
|
|
61
|
+
if self.must_revalidate {
|
|
62
|
+
directives.push("must-revalidate".to_owned());
|
|
63
|
+
}
|
|
64
|
+
if self.proxy_revalidate {
|
|
65
|
+
directives.push("proxy-revalidate".to_owned());
|
|
66
|
+
}
|
|
67
|
+
if self.immutable {
|
|
68
|
+
directives.push("immutable".to_owned());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Add age parameters
|
|
72
|
+
if let Some(max_age) = self.max_age {
|
|
73
|
+
directives.push(format!("max-age={}", max_age));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if let Some(s_max_age) = self.s_max_age {
|
|
77
|
+
directives.push(format!("s-maxage={}", s_max_age));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if let Some(stale_while_revalidate) = self.stale_while_revalidate {
|
|
81
|
+
directives.push(format!("stale-while-revalidate={}", stale_while_revalidate));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if let Some(stale_if_error) = self.stale_if_error {
|
|
85
|
+
directives.push(format!("stale-if-error={}", stale_if_error));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Set the Cache-Control header if we have directives
|
|
89
|
+
if !directives.is_empty() {
|
|
90
|
+
let cache_control_value = directives.join(", ");
|
|
91
|
+
debug!(target: "middleware::cache_control", "Built cache-control directive {}", cache_control_value);
|
|
92
|
+
self.cache_control_str.set(cache_control_value).unwrap();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Ok(())
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async fn after(&self, mut resp: HttpResponse, _: &mut HttpRequestContext) -> HttpResponse {
|
|
99
|
+
// Skip for statuses where caching doesn't make sense
|
|
100
|
+
let status = resp.status().as_u16();
|
|
101
|
+
if matches!(status, 401 | 403 | 500..=599) {
|
|
102
|
+
debug!(target: "middleware::cache_control", "Skipping cache-control for status {}", status);
|
|
103
|
+
return resp;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Set the Cache-Control header if we have directives
|
|
107
|
+
if let Some(cache_control_value) = self.cache_control_str.get() {
|
|
108
|
+
if let Ok(value) = HeaderValue::from_str(cache_control_value) {
|
|
109
|
+
debug!(target: "middleware::cache_control", "Setting cache-control header to {}", cache_control_value);
|
|
110
|
+
resp.headers_mut().insert("Cache-Control", value);
|
|
111
|
+
} else {
|
|
112
|
+
debug!(target: "middleware::cache_control", "Failed to parse cache-control value {}", cache_control_value);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
debug!(target: "middleware::cache_control", "No cache-control value provided");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Set Expires header based on max-age if present
|
|
119
|
+
if let Some(max_age) = self.max_age {
|
|
120
|
+
// Set the Expires header based on max-age
|
|
121
|
+
// Use a helper to format the HTTP date correctly
|
|
122
|
+
debug!(target: "middleware::cache_control", "Setting expires header to {}", max_age);
|
|
123
|
+
let expires = chrono::Utc::now() + chrono::Duration::seconds(max_age as i64);
|
|
124
|
+
let expires_str = expires.format("%a, %d %b %Y %H:%M:%S GMT").to_string();
|
|
125
|
+
if let Ok(value) = HeaderValue::from_str(&expires_str) {
|
|
126
|
+
resp.headers_mut().insert("Expires", value);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if !self.vary.is_empty() {
|
|
131
|
+
let vary_value = self.vary.join(", ");
|
|
132
|
+
if let Ok(value) = HeaderValue::from_str(&vary_value) {
|
|
133
|
+
resp.headers_mut().insert("Vary", value);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Set additional custom headers
|
|
138
|
+
for (name, value) in &self.additional_headers {
|
|
139
|
+
if let Ok(header_value) = HeaderValue::from_str(value) {
|
|
140
|
+
if let Ok(header_name) = name.parse::<HeaderName>() {
|
|
141
|
+
debug!(target: "middleware::cache_control", "Setting custom header {} to {:?}", header_name, header_value);
|
|
142
|
+
resp.headers_mut().insert(header_name, header_value);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
resp
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
impl FromValue for CacheControl {}
|