fix(workout): make mobile verification fail closed
Make workout mobile-verify reject non-HTTPS origins and release kits that omit detected external signing/toolchain blockers, so verification checks objective release preconditions instead of pretending to predict store approval. req: examples/006
This commit is contained in:
@@ -29,8 +29,11 @@ target/hemx-mobile/workout/
|
|||||||
ios/README.md
|
ios/README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `workout mobile-verify` to check the generated kit and release binary. Use
|
Use `workout mobile-verify` to check the generated kit and release binary. It
|
||||||
`workout doctor` when you only want to see missing external inputs.
|
fails on a non-HTTPS production origin, missing/inconsistent Android or iOS
|
||||||
|
metadata, or external toolchain/signing blockers that were not written into the
|
||||||
|
manifest and `BLOCKERS.md`. Use `workout doctor` when you only want to see
|
||||||
|
missing external inputs.
|
||||||
|
|
||||||
## Production configuration
|
## Production configuration
|
||||||
|
|
||||||
|
|||||||
+59
-2
@@ -261,7 +261,7 @@ fn verify_workout_mobile_release(
|
|||||||
config: &WorkoutMobileConfig,
|
config: &WorkoutMobileConfig,
|
||||||
require_server_binary: bool,
|
require_server_binary: bool,
|
||||||
) -> Vec<String> {
|
) -> Vec<String> {
|
||||||
let mut failures = Vec::new();
|
let mut failures = mobile_policy_failures(config);
|
||||||
if require_server_binary && !Path::new("target/release/hemx-workout-example").exists() {
|
if require_server_binary && !Path::new("target/release/hemx-workout-example").exists() {
|
||||||
failures.push(
|
failures.push(
|
||||||
"target/release/hemx-workout-example is missing; run workout-mobile release first"
|
"target/release/hemx-workout-example is missing; run workout-mobile release first"
|
||||||
@@ -329,6 +329,23 @@ fn verify_workout_mobile_release(
|
|||||||
],
|
],
|
||||||
&mut failures,
|
&mut failures,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for blocker in mobile_external_blockers(config) {
|
||||||
|
check_file_contains(&manifest_path, &[&blocker], &mut failures);
|
||||||
|
check_file_contains(&blockers_path, &[&blocker], &mut failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
failures
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mobile_policy_failures(config: &WorkoutMobileConfig) -> Vec<String> {
|
||||||
|
let mut failures = Vec::new();
|
||||||
|
if !config.origin.starts_with("https://") {
|
||||||
|
failures.push(
|
||||||
|
"HEMX_WORKOUT_ORIGIN must be a production HTTPS origin before mobile verification can pass"
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
failures
|
failures
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -797,6 +814,25 @@ mod tests {
|
|||||||
.any(|blocker| blocker.contains("production HTTPS origin")));
|
.any(|blocker| blocker.contains("production HTTPS origin")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn workout_mobile_verify_fails_closed_on_non_https_origin() {
|
||||||
|
// req: examples/006
|
||||||
|
let config = workout_mobile_config_at(
|
||||||
|
"http://workout.example.com",
|
||||||
|
"target/test-workout-mobile-non-https",
|
||||||
|
);
|
||||||
|
let _ = fs::remove_dir_all(&config.out_dir);
|
||||||
|
write_workout_mobile_release(&config, &mobile_external_blockers(&config))
|
||||||
|
.expect("write release kit");
|
||||||
|
|
||||||
|
let failures = verify_workout_mobile_release(&config, false);
|
||||||
|
|
||||||
|
assert!(failures
|
||||||
|
.iter()
|
||||||
|
.any(|failure| failure.contains("production HTTPS origin")));
|
||||||
|
let _ = fs::remove_dir_all(&config.out_dir);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn workout_mobile_verify_accepts_generated_release_kit() {
|
fn workout_mobile_verify_accepts_generated_release_kit() {
|
||||||
// req: examples/006
|
// req: examples/006
|
||||||
@@ -805,7 +841,7 @@ mod tests {
|
|||||||
"target/test-workout-mobile-accepts",
|
"target/test-workout-mobile-accepts",
|
||||||
);
|
);
|
||||||
let _ = fs::remove_dir_all(&config.out_dir);
|
let _ = fs::remove_dir_all(&config.out_dir);
|
||||||
write_workout_mobile_release(&config, &["Android signing key not configured".into()])
|
write_workout_mobile_release(&config, &mobile_external_blockers(&config))
|
||||||
.expect("write release kit");
|
.expect("write release kit");
|
||||||
|
|
||||||
assert!(verify_workout_mobile_release(&config, false).is_empty());
|
assert!(verify_workout_mobile_release(&config, false).is_empty());
|
||||||
@@ -813,6 +849,27 @@ mod tests {
|
|||||||
let _ = fs::remove_dir_all(&config.out_dir);
|
let _ = fs::remove_dir_all(&config.out_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn workout_mobile_verify_rejects_unacknowledged_external_blockers() {
|
||||||
|
// req: examples/006
|
||||||
|
let config = workout_mobile_config_at(
|
||||||
|
"https://workout.example.com",
|
||||||
|
"target/test-workout-mobile-unacknowledged-blockers",
|
||||||
|
);
|
||||||
|
let _ = fs::remove_dir_all(&config.out_dir);
|
||||||
|
write_workout_mobile_release(&config, &[]).expect("write release kit");
|
||||||
|
|
||||||
|
let failures = verify_workout_mobile_release(&config, false);
|
||||||
|
|
||||||
|
assert!(failures.iter().any(|failure| {
|
||||||
|
failure.contains("release-manifest.json") && failure.contains("Android")
|
||||||
|
}));
|
||||||
|
assert!(failures
|
||||||
|
.iter()
|
||||||
|
.any(|failure| failure.contains("BLOCKERS.md") && failure.contains("Android")));
|
||||||
|
let _ = fs::remove_dir_all(&config.out_dir);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn workout_mobile_verify_rejects_missing_release_kit() {
|
fn workout_mobile_verify_rejects_missing_release_kit() {
|
||||||
// req: examples/006
|
// req: examples/006
|
||||||
|
|||||||
Reference in New Issue
Block a user