hecks 3.11.0 → 3.12.0
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 +4 -4
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +6 -3
- data/lib/hecks/adapters/driving/cli_runner.rb +1 -0
- data/lib/hecks/adapters/driving/newcomer.rb +50 -0
- data/lib/hecks/bluebook/assembly/contracts/read_models.rb +1 -1
- data/lib/hecks/bluebook/assembly/marks.rb +3 -15
- data/lib/hecks/bluebook/assembly/scalars.rb +39 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +6 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +6 -3
- data/lib/hecks/bluebook.rb +1 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +16 -2
- data/lib/hecks/gate/stages.yml +32 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +5 -1
- data/lib/hecks/hecks/hecks.world +3 -0
- data/lib/hecks/language/bluebook/bluebook.behaviors +23 -0
- data/lib/hecks/language/bluebook/policy.behaviors +19 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +52 -0
- data/lib/hecks/projection_files/expression_tables.rb +7 -2
- data/lib/hecks/projections/deploy/box/compose.rb +6 -4
- data/lib/hecks/projections/deploy/box/edge.rb +62 -0
- data/lib/hecks/projections/deploy/box/logging.rb +99 -0
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +16 -8
- data/lib/hecks/projections/deploy/box/settings/cdn.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/logs.rb +40 -0
- data/lib/hecks/projections/deploy/box/settings/patterns.rb +11 -0
- data/lib/hecks/projections/deploy/box/settings/types.rb +16 -1
- data/lib/hecks/projections/deploy/box/settings.rb +6 -2
- data/lib/hecks/projections/deploy/box/stack_yaml.rb +5 -2
- data/lib/hecks/projections/deploy/box/templates/Makefile.tmpl +16 -0
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +8 -4
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +3 -0
- data/lib/hecks/projections/deploy/box/templates/edge.mk.tmpl +17 -0
- data/lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl +65 -0
- data/lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl +12 -0
- data/lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl +7 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +5 -4
- data/lib/hecks/projections/deploy/box.rb +15 -3
- data/lib/hecks/projections/site/site_root/workflow.rb +5 -0
- data/lib/hecks/projector/cli_audience.rb +20 -0
- data/lib/hecks/projector/cli_projector/usage.rb +15 -4
- data/lib/hecks/rust_build/host.rb +11 -4
- data/lib/hecks/rust_build/prebuilt_binary.rb +101 -0
- data/lib/hecks/tools/ci_gate_decision.rb +35 -0
- data/lib/hecks/tools/ci_gates.rb +20 -1
- data/lib/hecks/tools/lanes/files.rb +8 -0
- data/lib/hecks/tools/lanes/prebuilt_workflow.rb +81 -0
- data/lib/hecks/tools/lanes.rb +1 -0
- data/lib/hecks/tools/release_only.rb +111 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +15 -1
- data/prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal +0 -0
- data/prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal +0 -0
- data/prebuilt/{verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json → verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json} +1 -1
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/extension.rs +13 -1
- data/rust/host/src/lib.rs +1 -0
- data/rust/host/src/platform_signin/attack_tests.rs +1100 -0
- data/rust/host/src/platform_signin/config.rs +512 -0
- data/rust/host/src/platform_signin/cookie.rs +237 -0
- data/rust/host/src/platform_signin/extension.rs +190 -0
- data/rust/host/src/platform_signin/fetcher.rs +322 -0
- data/rust/host/src/platform_signin/handler.rs +251 -0
- data/rust/host/src/platform_signin/handler_tests.rs +554 -0
- data/rust/host/src/platform_signin/jti.rs +196 -0
- data/rust/host/src/platform_signin/jwks.rs +542 -0
- data/rust/host/src/platform_signin/jws.rs +249 -0
- data/rust/host/src/platform_signin/known_answer_tests.rs +237 -0
- data/rust/host/src/platform_signin/mapping_tests.rs +254 -0
- data/rust/host/src/platform_signin/mod.rs +75 -0
- data/rust/host/src/platform_signin/mode_tests.rs +382 -0
- data/rust/host/src/platform_signin/pages.rs +204 -0
- data/rust/host/src/platform_signin/query.rs +233 -0
- data/rust/host/src/platform_signin/session.rs +151 -0
- data/rust/host/src/platform_signin/settings.rs +385 -0
- data/rust/host/src/platform_signin/strict_json.rs +351 -0
- data/rust/host/src/platform_signin/test_support.rs +235 -0
- data/rust/host/src/platform_signin/ttl_tests.rs +94 -0
- data/rust/host/src/platform_signin/verify.rs +604 -0
- data/rust/host/src/platform_signin/wiring_support.rs +319 -0
- data/rust/host/src/web.rs +50 -6
- data/rust/parser/src/parse/value_object.rs +22 -10
- metadata +42 -4
- data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
//! Shared scaffolding for the wiring tests: settings, a recording log, a fake member directory,
|
|
2
|
+
//! and the known-answer vectors turned into callback requests.
|
|
3
|
+
|
|
4
|
+
use std::collections::HashMap;
|
|
5
|
+
use std::sync::{Arc, Mutex};
|
|
6
|
+
|
|
7
|
+
use async_trait::async_trait;
|
|
8
|
+
use serde_json::{json, Value};
|
|
9
|
+
|
|
10
|
+
use super::config::PlatformSigninConfig;
|
|
11
|
+
use super::handler::{CallbackRequest, Random, SigninHandler, SigninLog};
|
|
12
|
+
use super::jti::JtiStore;
|
|
13
|
+
use super::jwks::parse_jwks;
|
|
14
|
+
use super::session::MemberDirectory;
|
|
15
|
+
use super::settings::SigninSettings;
|
|
16
|
+
use super::test_support::StaticKeys;
|
|
17
|
+
use super::verify::Verifier;
|
|
18
|
+
use crate::auth::Session;
|
|
19
|
+
|
|
20
|
+
pub const SITE_URL: &str = "https://site.example";
|
|
21
|
+
pub const SECRET: &str = "a-long-session-secret-for-tests";
|
|
22
|
+
pub const ISSUER: &str = "https://issuer.example";
|
|
23
|
+
pub const NONCE_A: &str = "NonceNonceNonce0123456789abcdefghijklmn";
|
|
24
|
+
pub const STATE_A: &str = "StateStateState0123456789abcdefghijklmn";
|
|
25
|
+
pub const NONCE_B: &str = "NonceBNonceBNonceB0123456789abcdefghijk";
|
|
26
|
+
pub const STATE_B: &str = "StateBStateBStateB0123456789abcdefghijk";
|
|
27
|
+
|
|
28
|
+
/// The environment map a site with `mode` switched on would have.
|
|
29
|
+
pub fn env_map(mode: &str) -> HashMap<String, String> {
|
|
30
|
+
[
|
|
31
|
+
("PLATFORM_SIGNIN", mode),
|
|
32
|
+
("PLATFORM_ISSUER_URL", ISSUER),
|
|
33
|
+
("PLATFORM_SITE_ID", "site-a"),
|
|
34
|
+
("PLATFORM_ENVIRONMENT", "production"),
|
|
35
|
+
("SITE_URL", SITE_URL),
|
|
36
|
+
("SESSION_SECRET", SECRET),
|
|
37
|
+
("HECKS_SERVE_MODE", "1"),
|
|
38
|
+
]
|
|
39
|
+
.into_iter()
|
|
40
|
+
.map(|(k, v)| (k.to_string(), v.to_string()))
|
|
41
|
+
.collect()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Validated settings for `mode`.
|
|
45
|
+
pub fn settings(mode: &str) -> SigninSettings {
|
|
46
|
+
SigninSettings::from_env_map(&env_map(mode))
|
|
47
|
+
.expect("valid")
|
|
48
|
+
.expect("switched on")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// A log that keeps every event.
|
|
52
|
+
#[derive(Default)]
|
|
53
|
+
pub struct Recorder(Mutex<Vec<(String, Value)>>);
|
|
54
|
+
|
|
55
|
+
impl SigninLog for Recorder {
|
|
56
|
+
fn record(&self, event: &str, fields: Value) {
|
|
57
|
+
self.0.lock().unwrap().push((event.to_string(), fields));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl Recorder {
|
|
62
|
+
/// Every recorded event as one text, for searching.
|
|
63
|
+
pub fn text(&self) -> String {
|
|
64
|
+
self.0
|
|
65
|
+
.lock()
|
|
66
|
+
.unwrap()
|
|
67
|
+
.iter()
|
|
68
|
+
.map(|(e, f)| format!("{e} {f}"))
|
|
69
|
+
.collect::<Vec<_>>()
|
|
70
|
+
.join("\n")
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// The names of the recorded events, in order.
|
|
74
|
+
pub fn events(&self) -> Vec<String> {
|
|
75
|
+
self.0
|
|
76
|
+
.lock()
|
|
77
|
+
.unwrap()
|
|
78
|
+
.iter()
|
|
79
|
+
.map(|(e, _)| e.clone())
|
|
80
|
+
.collect()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// The fields of the first event called `name`.
|
|
84
|
+
pub fn fields_of(&self, name: &str) -> Option<Value> {
|
|
85
|
+
self.0
|
|
86
|
+
.lock()
|
|
87
|
+
.unwrap()
|
|
88
|
+
.iter()
|
|
89
|
+
.find(|(e, _)| e == name)
|
|
90
|
+
.map(|(_, f)| f.clone())
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// A person the fake store knows.
|
|
95
|
+
#[derive(Clone)]
|
|
96
|
+
pub struct Person {
|
|
97
|
+
pub email: String,
|
|
98
|
+
pub role: Option<String>,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
impl Person {
|
|
102
|
+
/// Builds a person with this email and local role.
|
|
103
|
+
pub fn new(email: &str, role: Option<&str>) -> Self {
|
|
104
|
+
Self {
|
|
105
|
+
email: email.into(),
|
|
106
|
+
role: role.map(str::to_string),
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn session(&self) -> Session {
|
|
111
|
+
Session {
|
|
112
|
+
identity_id: "identity-1".into(),
|
|
113
|
+
email: self.email.clone(),
|
|
114
|
+
name: "Person".into(),
|
|
115
|
+
role: self.role.clone(),
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// A member store with scripted answers that remembers what it was asked.
|
|
121
|
+
#[derive(Default)]
|
|
122
|
+
pub struct FakeMembers {
|
|
123
|
+
/// `(issuer, subject)` -> identity id.
|
|
124
|
+
pub identities: HashMap<(String, String), String>,
|
|
125
|
+
/// identity id -> the linked member.
|
|
126
|
+
pub linked: HashMap<String, Person>,
|
|
127
|
+
/// What linking by email answers.
|
|
128
|
+
pub provisions: Option<Person>,
|
|
129
|
+
pub fail: bool,
|
|
130
|
+
pub calls: Mutex<Vec<String>>,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
impl FakeMembers {
|
|
134
|
+
/// A store where the vectors' identity resolves to `person`.
|
|
135
|
+
pub fn resolving(person: Person) -> Self {
|
|
136
|
+
let mut store = Self::default();
|
|
137
|
+
store.identities.insert(
|
|
138
|
+
("https://idp.example".into(), "idp-subject-0001".into()),
|
|
139
|
+
"identity-1".into(),
|
|
140
|
+
);
|
|
141
|
+
store.linked.insert("identity-1".into(), person);
|
|
142
|
+
store
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// A store where nothing resolves and linking by email yields `person`.
|
|
146
|
+
pub fn provisioning(person: Person) -> Self {
|
|
147
|
+
Self {
|
|
148
|
+
provisions: Some(person),
|
|
149
|
+
..Self::default()
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Lists the calls made so far, in order.
|
|
154
|
+
pub fn called(&self) -> Vec<String> {
|
|
155
|
+
self.calls.lock().unwrap().clone()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn note(&self, call: &str) -> anyhow::Result<()> {
|
|
159
|
+
self.calls.lock().unwrap().push(call.to_string());
|
|
160
|
+
if self.fail {
|
|
161
|
+
anyhow::bail!("the store is down");
|
|
162
|
+
}
|
|
163
|
+
Ok(())
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[async_trait]
|
|
168
|
+
impl MemberDirectory for FakeMembers {
|
|
169
|
+
async fn identity_for(&self, issuer: &str, subject: &str) -> anyhow::Result<Option<String>> {
|
|
170
|
+
self.note("identity_for")?;
|
|
171
|
+
Ok(self
|
|
172
|
+
.identities
|
|
173
|
+
.get(&(issuer.to_string(), subject.to_string()))
|
|
174
|
+
.cloned())
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async fn session_for_identity(&self, identity_id: &str) -> anyhow::Result<Option<Session>> {
|
|
178
|
+
self.note("session_for_identity")?;
|
|
179
|
+
Ok(self.linked.get(identity_id).map(Person::session))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async fn provision(
|
|
183
|
+
&self,
|
|
184
|
+
email: &str,
|
|
185
|
+
issuer: &str,
|
|
186
|
+
subject: &str,
|
|
187
|
+
) -> anyhow::Result<Option<Session>> {
|
|
188
|
+
self.note(&format!("provision {email} {issuer} {subject}"))?;
|
|
189
|
+
Ok(self.provisions.as_ref().map(Person::session))
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// The known-answer file.
|
|
194
|
+
pub fn vectors() -> Value {
|
|
195
|
+
let path = concat!(
|
|
196
|
+
env!("CARGO_MANIFEST_DIR"),
|
|
197
|
+
"/tests/fixtures/platform_signin_vectors.json"
|
|
198
|
+
);
|
|
199
|
+
serde_json::from_slice(&std::fs::read(path).expect("vectors")).expect("json")
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// A verifier that trusts the vectors' key set and pins its issuer, site and environment.
|
|
203
|
+
pub fn vector_verifier(doc: &Value) -> Verifier<StaticKeys> {
|
|
204
|
+
let pins = &doc["config"];
|
|
205
|
+
let text = |name: &str| pins[name].as_str().expect("string").to_string();
|
|
206
|
+
let config = PlatformSigninConfig::new(
|
|
207
|
+
&text("iss"),
|
|
208
|
+
&format!("{}/.well-known/jwks.json", text("iss")),
|
|
209
|
+
&text("aud"),
|
|
210
|
+
&text("env"),
|
|
211
|
+
);
|
|
212
|
+
let parsed = parse_jwks(&doc["jwks"].to_string()).expect("key set");
|
|
213
|
+
Verifier::new(config, StaticKeys(parsed.keys), JtiStore::new())
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// Percent-encodes everything outside the unreserved set.
|
|
217
|
+
pub fn encode(text: &str) -> String {
|
|
218
|
+
text.bytes()
|
|
219
|
+
.map(|b| {
|
|
220
|
+
if b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_' | b'.' | b'~') {
|
|
221
|
+
(b as char).to_string()
|
|
222
|
+
} else {
|
|
223
|
+
format!("%{b:02X}")
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
.collect()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// The callback request a browser would make for one vector step.
|
|
230
|
+
pub fn request_for(step: &Value) -> CallbackRequest {
|
|
231
|
+
let mut query = Vec::new();
|
|
232
|
+
if let Some(token) = step["token"].as_str() {
|
|
233
|
+
query.push(format!("token={}", encode(token)));
|
|
234
|
+
}
|
|
235
|
+
if let Some(state) = step["state"].as_str() {
|
|
236
|
+
query.push(format!("state={}", encode(state)));
|
|
237
|
+
}
|
|
238
|
+
let cookie_pairs = match step.get("cookie").filter(|c| !c.is_null()) {
|
|
239
|
+
None => vec![],
|
|
240
|
+
Some(cookie) => {
|
|
241
|
+
let part = |n: &str| cookie.get(n).and_then(Value::as_str);
|
|
242
|
+
let value = match part("state") {
|
|
243
|
+
Some(state) => format!("{}.{state}", part("nonce").unwrap_or("")),
|
|
244
|
+
None => part("nonce").unwrap_or("").to_string(),
|
|
245
|
+
};
|
|
246
|
+
vec![format!("__Host-signin={value}")]
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
CallbackRequest {
|
|
250
|
+
raw_query: query.join("&"),
|
|
251
|
+
cookie_pairs,
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/// A random source that hands out `values` in order.
|
|
256
|
+
pub fn scripted_random(values: &[&str]) -> Random {
|
|
257
|
+
let queue = Mutex::new(
|
|
258
|
+
values
|
|
259
|
+
.iter()
|
|
260
|
+
.map(|v| v.to_string())
|
|
261
|
+
.collect::<std::collections::VecDeque<_>>(),
|
|
262
|
+
);
|
|
263
|
+
Box::new(move || queue.lock().unwrap().pop_front().expect("a scripted value"))
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/// A handler over the vectors' verifier with a fixed clock and a recording log.
|
|
267
|
+
pub fn vector_handler(mode: &str, log: Arc<Recorder>) -> SigninHandler {
|
|
268
|
+
let doc = vectors();
|
|
269
|
+
let now = doc["now"].as_i64().expect("now");
|
|
270
|
+
SigninHandler::new(settings(mode), Arc::new(vector_verifier(&doc)), log)
|
|
271
|
+
.with_clock(Box::new(move || now))
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/// The verified claims of an accepted vector.
|
|
275
|
+
pub fn vector_claims(doc: &Value, name: &str) -> super::verify::VerifiedClaims {
|
|
276
|
+
use super::verify::{SigninCookie, SigninRequest};
|
|
277
|
+
let step = single(doc, name);
|
|
278
|
+
let cookie = SigninCookie {
|
|
279
|
+
nonce: step["cookie"]["nonce"].as_str().unwrap().to_string(),
|
|
280
|
+
state: step["cookie"]["state"].as_str().unwrap().to_string(),
|
|
281
|
+
};
|
|
282
|
+
let request = SigninRequest {
|
|
283
|
+
token: step["token"].as_str(),
|
|
284
|
+
state: step["state"].as_str(),
|
|
285
|
+
cookie: Some(&cookie),
|
|
286
|
+
};
|
|
287
|
+
vector_verifier(doc)
|
|
288
|
+
.verify(&request, doc["now"].as_i64().unwrap())
|
|
289
|
+
.expect("an accepted vector")
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// The cookies of a response.
|
|
293
|
+
pub fn cookies_of(response: &Value) -> Vec<String> {
|
|
294
|
+
response
|
|
295
|
+
.get("cookies")
|
|
296
|
+
.and_then(Value::as_array)
|
|
297
|
+
.map(|a| {
|
|
298
|
+
a.iter()
|
|
299
|
+
.filter_map(|c| c.as_str().map(str::to_string))
|
|
300
|
+
.collect()
|
|
301
|
+
})
|
|
302
|
+
.unwrap_or_default()
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/// The vector with this name.
|
|
306
|
+
pub fn single(doc: &Value, name: &str) -> Value {
|
|
307
|
+
doc["vectors"]
|
|
308
|
+
.as_array()
|
|
309
|
+
.unwrap()
|
|
310
|
+
.iter()
|
|
311
|
+
.find(|v| v["name"] == name)
|
|
312
|
+
.unwrap_or_else(|| panic!("vector {name}"))
|
|
313
|
+
.clone()
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#[allow(dead_code)]
|
|
317
|
+
fn _unused() -> Value {
|
|
318
|
+
json!({})
|
|
319
|
+
}
|
data/rust/host/src/web.rs
CHANGED
|
@@ -1686,17 +1686,29 @@ fn redirect(location: &str) -> Value {
|
|
|
1686
1686
|
json!({"statusCode": 302, "headers": {"location": location}, "body": "", "isBase64Encoded": false})
|
|
1687
1687
|
}
|
|
1688
1688
|
|
|
1689
|
-
// HttpOnly + SameSite always; Secure
|
|
1690
|
-
//
|
|
1689
|
+
// HttpOnly + SameSite always; Secure unless the host's own address is plain http — a localhost
|
|
1690
|
+
// http:// callback can't set a Secure cookie. See `account_cookie_flags` for which address counts.
|
|
1691
1691
|
fn cookie_flags() -> &'static str {
|
|
1692
|
-
let
|
|
1693
|
-
|
|
1694
|
-
|
|
1692
|
+
let redirect_uri = std::env::var("GOOGLE_REDIRECT_URI").unwrap_or_default();
|
|
1693
|
+
let site_url = std::env::var("SITE_URL").unwrap_or_default();
|
|
1694
|
+
account_cookie_flags(&redirect_uri, &site_url)
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// The flags for the account cookie given the Google redirect URI and the site's public address.
|
|
1698
|
+
// A host that sets the redirect URI is judged by it, as ever; a host that has stopped setting it
|
|
1699
|
+
// is judged by SITE_URL, and with neither set the cookie is Secure.
|
|
1700
|
+
pub(crate) fn account_cookie_flags(redirect_uri: &str, site_url: &str) -> &'static str {
|
|
1701
|
+
let judged_by = if redirect_uri.is_empty() { site_url } else { redirect_uri };
|
|
1702
|
+
if judged_by.starts_with("http://") {
|
|
1703
|
+
COOKIE_FLAGS_PLAIN
|
|
1695
1704
|
} else {
|
|
1696
|
-
|
|
1705
|
+
COOKIE_FLAGS_SECURE
|
|
1697
1706
|
}
|
|
1698
1707
|
}
|
|
1699
1708
|
|
|
1709
|
+
pub(crate) const COOKIE_FLAGS_SECURE: &str = "; Path=/; HttpOnly; Secure; SameSite=Lax";
|
|
1710
|
+
const COOKIE_FLAGS_PLAIN: &str = "; Path=/; HttpOnly; SameSite=Lax";
|
|
1711
|
+
|
|
1700
1712
|
fn redirect_with_cookie(location: &str, cookie: &str) -> Value {
|
|
1701
1713
|
json!({"statusCode": 302, "headers": {"location": location}, "cookies": [cookie], "body": "", "isBase64Encoded": false})
|
|
1702
1714
|
}
|
|
@@ -2985,6 +2997,38 @@ mod tests {
|
|
|
2985
2997
|
|
|
2986
2998
|
}
|
|
2987
2999
|
|
|
3000
|
+
#[cfg(test)]
|
|
3001
|
+
mod cookie_flag_tests {
|
|
3002
|
+
use super::account_cookie_flags;
|
|
3003
|
+
|
|
3004
|
+
const SECURE: &str = "; Path=/; HttpOnly; Secure; SameSite=Lax";
|
|
3005
|
+
const PLAIN: &str = "; Path=/; HttpOnly; SameSite=Lax";
|
|
3006
|
+
|
|
3007
|
+
#[test]
|
|
3008
|
+
fn a_host_that_sets_the_redirect_uri_is_judged_by_it_as_before() {
|
|
3009
|
+
assert_eq!(account_cookie_flags("https://site.example/auth/google/callback", ""), SECURE);
|
|
3010
|
+
assert_eq!(account_cookie_flags("http://localhost:4567/auth/google/callback", ""), PLAIN);
|
|
3011
|
+
assert_eq!(account_cookie_flags("https://site.example/cb", "http://localhost:4321"), SECURE);
|
|
3012
|
+
assert_eq!(account_cookie_flags("http://localhost:4567/cb", "https://site.example"), PLAIN);
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
#[test]
|
|
3016
|
+
fn a_host_with_no_redirect_uri_and_an_https_site_url_still_sets_secure() {
|
|
3017
|
+
assert_eq!(account_cookie_flags("", "https://site.example"), SECURE);
|
|
3018
|
+
assert_eq!(account_cookie_flags("", "https://site.example/"), SECURE);
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
#[test]
|
|
3022
|
+
fn a_host_with_no_redirect_uri_and_an_http_site_url_does_not_demand_secure() {
|
|
3023
|
+
assert_eq!(account_cookie_flags("", "http://localhost:4321"), PLAIN);
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
#[test]
|
|
3027
|
+
fn a_host_with_neither_setting_stays_secure() {
|
|
3028
|
+
assert_eq!(account_cookie_flags("", ""), SECURE);
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
|
|
2988
3032
|
#[cfg(test)]
|
|
2989
3033
|
#[path = "boundary_fuzz/web.rs"]
|
|
2990
3034
|
mod boundary_fuzz;
|
|
@@ -140,7 +140,7 @@ pub fn parse_body(
|
|
|
140
140
|
fn build_value_object_attribute(
|
|
141
141
|
file: &str,
|
|
142
142
|
args: &super::ArgumentGateResult,
|
|
143
|
-
) -> ParseResult<(ir::Attribute, Option<Vec<
|
|
143
|
+
) -> ParseResult<(ir::Attribute, Option<Vec<ruby_value::Value>>, Option<ir::ValueObject>)> {
|
|
144
144
|
let (attribute, closed_set) = super::build_attribute(file, 0, "attribute", args)?;
|
|
145
145
|
let one_of = super::named_raw(args, "one_of").map(|raw| {
|
|
146
146
|
let trimmed = raw.trim();
|
|
@@ -150,7 +150,7 @@ fn build_value_object_attribute(
|
|
|
150
150
|
.unwrap_or(trimmed);
|
|
151
151
|
ruby_value::split_items(inner)
|
|
152
152
|
.into_iter()
|
|
153
|
-
.map(|segment|
|
|
153
|
+
.map(|segment| cell_value(segment.trim()))
|
|
154
154
|
.collect()
|
|
155
155
|
});
|
|
156
156
|
Ok((attribute, one_of, closed_set))
|
|
@@ -164,7 +164,7 @@ fn install_inline_closed_set(
|
|
|
164
164
|
vo_name: &str,
|
|
165
165
|
vo: &mut ir::ValueObject,
|
|
166
166
|
field: &str,
|
|
167
|
-
values: &[
|
|
167
|
+
values: &[ruby_value::Value],
|
|
168
168
|
) -> ParseResult<()> {
|
|
169
169
|
if !vo.attributes.is_empty() {
|
|
170
170
|
return Err(Diagnostic::new(
|
|
@@ -182,11 +182,11 @@ fn install_inline_closed_set(
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
vo.closed_set = true;
|
|
185
|
-
//
|
|
186
|
-
// `
|
|
185
|
+
// Read like a `member` row (`push_member`): a quoted value stays a String and a bare
|
|
186
|
+
// `true`, `false` or number keeps its type, as Ruby's builder holds them.
|
|
187
187
|
vo.members = values
|
|
188
188
|
.iter()
|
|
189
|
-
.map(|value| vec![(field.to_string(),
|
|
189
|
+
.map(|value| vec![(field.to_string(), value.clone())])
|
|
190
190
|
.collect();
|
|
191
191
|
Ok(())
|
|
192
192
|
}
|
|
@@ -207,15 +207,27 @@ fn push_member(
|
|
|
207
207
|
let fields = args
|
|
208
208
|
.named
|
|
209
209
|
.iter()
|
|
210
|
-
.map(|(field, raw)| (field.clone(),
|
|
210
|
+
.map(|(field, raw)| (field.clone(), cell_value(raw)))
|
|
211
211
|
.collect();
|
|
212
212
|
members.push(fields);
|
|
213
213
|
Ok(())
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
///
|
|
217
|
-
///
|
|
218
|
-
///
|
|
216
|
+
/// Reads one member cell from its source spelling, as `Scalars.unmark` reads a stored one
|
|
217
|
+
/// (lib/hecks/bluebook/assembly/scalars.rb): a quoted cell is a String whatever it spells, and a
|
|
218
|
+
/// bare boolean or number keeps its type. Anything else is read through its text.
|
|
219
|
+
fn cell_value(raw: &str) -> ruby_value::Value {
|
|
220
|
+
match ruby_value::read(raw) {
|
|
221
|
+
held @ (ruby_value::Value::Str(_)
|
|
222
|
+
| ruby_value::Value::Bool(_)
|
|
223
|
+
| ruby_value::Value::Int(_)
|
|
224
|
+
| ruby_value::Value::Float(_)) => held,
|
|
225
|
+
other => unmark_scalar(&ruby_value::to_s(&other)),
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// The text-shape half of `Scalars.unmark`: for a cell that is neither a quoted String nor a
|
|
230
|
+
/// bare boolean or number, `true`, `false` and number-shaped text read back typed.
|
|
219
231
|
fn unmark_scalar(text: &str) -> ruby_value::Value {
|
|
220
232
|
if text == "true" {
|
|
221
233
|
return ruby_value::Value::Bool(true);
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hecks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Young
|
|
@@ -145,6 +145,7 @@ files:
|
|
|
145
145
|
- lib/hecks/adapters/driving/mcp_scope/argument_policy.rb
|
|
146
146
|
- lib/hecks/adapters/driving/mcp_scope/env_problems.rb
|
|
147
147
|
- lib/hecks/adapters/driving/mcp_scope/presenter.rb
|
|
148
|
+
- lib/hecks/adapters/driving/newcomer.rb
|
|
148
149
|
- lib/hecks/adapters/driving/ruby.rb
|
|
149
150
|
- lib/hecks/adapters/driving/ruby/aggregate_module.rb
|
|
150
151
|
- lib/hecks/adapters/driving/ruby/chapter.rb
|
|
@@ -183,6 +184,7 @@ files:
|
|
|
183
184
|
- lib/hecks/bluebook/assembly/contracts/sagas.rb
|
|
184
185
|
- lib/hecks/bluebook/assembly/contracts/structure.rb
|
|
185
186
|
- lib/hecks/bluebook/assembly/marks.rb
|
|
187
|
+
- lib/hecks/bluebook/assembly/scalars.rb
|
|
186
188
|
- lib/hecks/bluebook/assembly/specializer.rb
|
|
187
189
|
- lib/hecks/bluebook/attribute.rb
|
|
188
190
|
- lib/hecks/bluebook/behaviour/aggregate.rb
|
|
@@ -699,10 +701,12 @@ files:
|
|
|
699
701
|
- lib/hecks/language/adapter.bluebook
|
|
700
702
|
- lib/hecks/language/bluebook/aggregate.bluebook
|
|
701
703
|
- lib/hecks/language/bluebook/attaches/paging.bluebook
|
|
704
|
+
- lib/hecks/language/bluebook/bluebook.behaviors
|
|
702
705
|
- lib/hecks/language/bluebook/bluebook.bluebook
|
|
703
706
|
- lib/hecks/language/bluebook/bluebook.hecksagon
|
|
704
707
|
- lib/hecks/language/bluebook/command.bluebook
|
|
705
708
|
- lib/hecks/language/bluebook/entity.bluebook
|
|
709
|
+
- lib/hecks/language/bluebook/policy.behaviors
|
|
706
710
|
- lib/hecks/language/bluebook/policy.bluebook
|
|
707
711
|
- lib/hecks/language/bluebook/process_manager.bluebook
|
|
708
712
|
- lib/hecks/language/bluebook/projection.bluebook
|
|
@@ -837,14 +841,18 @@ files:
|
|
|
837
841
|
- lib/hecks/projections/deploy/box/access.rb
|
|
838
842
|
- lib/hecks/projections/deploy/box/caddy.rb
|
|
839
843
|
- lib/hecks/projections/deploy/box/compose.rb
|
|
844
|
+
- lib/hecks/projections/deploy/box/edge.rb
|
|
840
845
|
- lib/hecks/projections/deploy/box/hosting.rb
|
|
841
846
|
- lib/hecks/projections/deploy/box/hosting_parts.rb
|
|
842
847
|
- lib/hecks/projections/deploy/box/hosting_settings.rb
|
|
848
|
+
- lib/hecks/projections/deploy/box/logging.rb
|
|
843
849
|
- lib/hecks/projections/deploy/box/roll_recipe.rb
|
|
844
850
|
- lib/hecks/projections/deploy/box/roll_scripts.rb
|
|
845
851
|
- lib/hecks/projections/deploy/box/settings.rb
|
|
852
|
+
- lib/hecks/projections/deploy/box/settings/cdn.rb
|
|
846
853
|
- lib/hecks/projections/deploy/box/settings/checks.rb
|
|
847
854
|
- lib/hecks/projections/deploy/box/settings/containers.rb
|
|
855
|
+
- lib/hecks/projections/deploy/box/settings/logs.rb
|
|
848
856
|
- lib/hecks/projections/deploy/box/settings/origin.rb
|
|
849
857
|
- lib/hecks/projections/deploy/box/settings/patterns.rb
|
|
850
858
|
- lib/hecks/projections/deploy/box/settings/types.rb
|
|
@@ -861,8 +869,12 @@ files:
|
|
|
861
869
|
- lib/hecks/projections/deploy/box/templates/deploy-usage-services.tmpl
|
|
862
870
|
- lib/hecks/projections/deploy/box/templates/deploy-usage-taskdef.tmpl
|
|
863
871
|
- lib/hecks/projections/deploy/box/templates/ecr-repository.yaml.tmpl
|
|
872
|
+
- lib/hecks/projections/deploy/box/templates/edge.mk.tmpl
|
|
873
|
+
- lib/hecks/projections/deploy/box/templates/edge.yaml.tmpl
|
|
864
874
|
- lib/hecks/projections/deploy/box/templates/fetch-secrets.sh
|
|
865
875
|
- lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl
|
|
876
|
+
- lib/hecks/projections/deploy/box/templates/log-group.yaml.tmpl
|
|
877
|
+
- lib/hecks/projections/deploy/box/templates/log-policy.yaml.tmpl
|
|
866
878
|
- lib/hecks/projections/deploy/box/templates/mode-summary-services.tmpl
|
|
867
879
|
- lib/hecks/projections/deploy/box/templates/mode-summary-taskdef.tmpl
|
|
868
880
|
- lib/hecks/projections/deploy/box/templates/provision-database.sh.tmpl
|
|
@@ -1481,6 +1493,7 @@ files:
|
|
|
1481
1493
|
- lib/hecks/rust_build/host.rb
|
|
1482
1494
|
- lib/hecks/rust_build/kernel_input.rb
|
|
1483
1495
|
- lib/hecks/rust_build/native_build.rb
|
|
1496
|
+
- lib/hecks/rust_build/prebuilt_binary.rb
|
|
1484
1497
|
- lib/hecks/rust_build/project_rust.rb
|
|
1485
1498
|
- lib/hecks/rust_build/project_rust/codegen_run.rb
|
|
1486
1499
|
- lib/hecks/rust_build/project_rust/domain_loader.rb
|
|
@@ -1559,6 +1572,7 @@ files:
|
|
|
1559
1572
|
- lib/hecks/tools/lanes.rb
|
|
1560
1573
|
- lib/hecks/tools/lanes/files.rb
|
|
1561
1574
|
- lib/hecks/tools/lanes/live.rb
|
|
1575
|
+
- lib/hecks/tools/lanes/prebuilt_workflow.rb
|
|
1562
1576
|
- lib/hecks/tools/lanes/rows.rb
|
|
1563
1577
|
- lib/hecks/tools/lanes/ruleset.rb
|
|
1564
1578
|
- lib/hecks/tools/lanes/watch_workflow.rb
|
|
@@ -1567,6 +1581,7 @@ files:
|
|
|
1567
1581
|
- lib/hecks/tools/oidc_manifests.rb
|
|
1568
1582
|
- lib/hecks/tools/query_ir_run.rb
|
|
1569
1583
|
- lib/hecks/tools/regeneration_run.rb
|
|
1584
|
+
- lib/hecks/tools/release_only.rb
|
|
1570
1585
|
- lib/hecks/tools/rust_comment_style.rb
|
|
1571
1586
|
- lib/hecks/tools/rust_comment_style/caps_words.rb
|
|
1572
1587
|
- lib/hecks/tools/rust_comment_style/fixing.rb
|
|
@@ -1607,9 +1622,9 @@ files:
|
|
|
1607
1622
|
- lib/rubocop/cop/hecks/symbol_string_key_mix.rb
|
|
1608
1623
|
- lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb
|
|
1609
1624
|
- lib/rubocop/cop/hecks/untyped_raise.rb
|
|
1610
|
-
- prebuilt/
|
|
1611
|
-
- prebuilt/
|
|
1612
|
-
- prebuilt/verdicts-
|
|
1625
|
+
- prebuilt/9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal
|
|
1626
|
+
- prebuilt/d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal
|
|
1627
|
+
- prebuilt/verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json
|
|
1613
1628
|
- qa/settings.yml
|
|
1614
1629
|
- rust/.cargo/config.toml
|
|
1615
1630
|
- rust/.cargo/mutants.toml
|
|
@@ -1706,6 +1721,29 @@ files:
|
|
|
1706
1721
|
- rust/host/src/payments.rs
|
|
1707
1722
|
- rust/host/src/payments/keystore.rs
|
|
1708
1723
|
- rust/host/src/payments/tests.rs
|
|
1724
|
+
- rust/host/src/platform_signin/attack_tests.rs
|
|
1725
|
+
- rust/host/src/platform_signin/config.rs
|
|
1726
|
+
- rust/host/src/platform_signin/cookie.rs
|
|
1727
|
+
- rust/host/src/platform_signin/extension.rs
|
|
1728
|
+
- rust/host/src/platform_signin/fetcher.rs
|
|
1729
|
+
- rust/host/src/platform_signin/handler.rs
|
|
1730
|
+
- rust/host/src/platform_signin/handler_tests.rs
|
|
1731
|
+
- rust/host/src/platform_signin/jti.rs
|
|
1732
|
+
- rust/host/src/platform_signin/jwks.rs
|
|
1733
|
+
- rust/host/src/platform_signin/jws.rs
|
|
1734
|
+
- rust/host/src/platform_signin/known_answer_tests.rs
|
|
1735
|
+
- rust/host/src/platform_signin/mapping_tests.rs
|
|
1736
|
+
- rust/host/src/platform_signin/mod.rs
|
|
1737
|
+
- rust/host/src/platform_signin/mode_tests.rs
|
|
1738
|
+
- rust/host/src/platform_signin/pages.rs
|
|
1739
|
+
- rust/host/src/platform_signin/query.rs
|
|
1740
|
+
- rust/host/src/platform_signin/session.rs
|
|
1741
|
+
- rust/host/src/platform_signin/settings.rs
|
|
1742
|
+
- rust/host/src/platform_signin/strict_json.rs
|
|
1743
|
+
- rust/host/src/platform_signin/test_support.rs
|
|
1744
|
+
- rust/host/src/platform_signin/ttl_tests.rs
|
|
1745
|
+
- rust/host/src/platform_signin/verify.rs
|
|
1746
|
+
- rust/host/src/platform_signin/wiring_support.rs
|
|
1709
1747
|
- rust/host/src/presentation.rs
|
|
1710
1748
|
- rust/host/src/presentation_write.rs
|
|
1711
1749
|
- rust/host/src/query_step.rs
|
|
Binary file
|