mirror of
https://github.com/block/buzz.git
synced 2026-11-07 09:52:03 +00:00
Expose one set of current policy preconditions
Exercise retained historical receipts and deleted targets through the supported status API; refuse UUID reuse without mutating policy. Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz> Signed-off-by: Brad Seiler <seiler@squareup.com>
This commit is contained in:
@@ -199,7 +199,6 @@ pub async fn community_nip_fi_policy_status(
|
||||
let fields = mode_fields(&state, Some(policy.requires_nip_fi));
|
||||
Ok(Json(serde_json::json!({
|
||||
"community_id": id, "expected_host": host,
|
||||
"requires_nip_fi": policy.requires_nip_fi, "policy_generation": policy.policy_generation,
|
||||
"expected_policy_generation": policy.policy_generation,
|
||||
"expected_requires_nip_fi": policy.requires_nip_fi,
|
||||
"mode": fields["mode"], "effective_required": fields["effective_required"],
|
||||
|
||||
@@ -66,7 +66,64 @@ mod postgres_tests {
|
||||
.await;
|
||||
assert_eq!(disabled.status(), StatusCode::SERVICE_UNAVAILABLE);
|
||||
assert_eq!(read_json(disabled).await["code"], "upgrades_disabled");
|
||||
for (field, value) in [
|
||||
("operation_id", serde_json::json!(Uuid::nil())),
|
||||
("community_id", serde_json::json!(Uuid::nil())),
|
||||
("expected_host", serde_json::json!(host.to_uppercase())),
|
||||
("actor_pubkey", serde_json::json!("not-a-pubkey")),
|
||||
("expected_policy_generation", serde_json::json!(-1)),
|
||||
("expected_issuer", serde_json::json!("")),
|
||||
("unknown", serde_json::json!(true)),
|
||||
] {
|
||||
let mut invalid = body.clone();
|
||||
invalid[field] = value;
|
||||
let response = fresh_upgrade_request(
|
||||
state.clone(),
|
||||
&operator,
|
||||
"POST",
|
||||
path,
|
||||
Some(invalid.to_string()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST, "{field}");
|
||||
assert_eq!(read_json(response).await["code"], "invalid_request");
|
||||
}
|
||||
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config).nip_fi_changes = true;
|
||||
for (field, value, code) in [
|
||||
(
|
||||
"expected_issuer",
|
||||
"https://unapproved.example".to_owned(),
|
||||
"issuer_conflict",
|
||||
),
|
||||
(
|
||||
"expected_host",
|
||||
"outside.example".to_owned(),
|
||||
"issuer_conflict",
|
||||
),
|
||||
(
|
||||
"actor_pubkey",
|
||||
Keys::generate().public_key().to_hex(),
|
||||
"owner_conflict",
|
||||
),
|
||||
(
|
||||
"expected_owner_pubkey",
|
||||
Keys::generate().public_key().to_hex(),
|
||||
"owner_conflict",
|
||||
),
|
||||
] {
|
||||
let mut refused = body.clone();
|
||||
refused[field] = value.into();
|
||||
let response = fresh_upgrade_request(
|
||||
state.clone(),
|
||||
&operator,
|
||||
"POST",
|
||||
path,
|
||||
Some(refused.to_string()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(response.status(), StatusCode::CONFLICT);
|
||||
assert_eq!(read_json(response).await["code"], code);
|
||||
}
|
||||
let mut history = Vec::new();
|
||||
for (generation, desired) in [(1, true), (2, false), (3, true)] {
|
||||
body["operation_id"] = Uuid::new_v4().to_string().into();
|
||||
@@ -96,6 +153,73 @@ mod postgres_tests {
|
||||
accepted["receipt"]["state"],
|
||||
if desired { "pending" } else { "applied" }
|
||||
);
|
||||
if generation == 1 {
|
||||
let binding = Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config)
|
||||
.nip_fi_provision_binding
|
||||
.take();
|
||||
let mut fresh = body.clone();
|
||||
let refused_id = Uuid::new_v4();
|
||||
fresh["operation_id"] = refused_id.to_string().into();
|
||||
fresh["expected_policy_generation"] = 1.into();
|
||||
fresh["expected_requires_nip_fi"] = true.into();
|
||||
let refused = fresh_upgrade_request(
|
||||
state.clone(),
|
||||
&operator,
|
||||
"POST",
|
||||
path,
|
||||
Some(fresh.to_string()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(refused.status(), StatusCode::SERVICE_UNAVAILABLE);
|
||||
assert_eq!(
|
||||
read_json(refused).await["code"],
|
||||
"authorization_unavailable"
|
||||
);
|
||||
assert!(state
|
||||
.db
|
||||
.community_auth_policy_status(refused_id, &operator.public_key().to_hex())
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none());
|
||||
let historical = read_json(
|
||||
fresh_upgrade_request(
|
||||
state.clone(),
|
||||
&operator,
|
||||
"POST",
|
||||
path,
|
||||
Some(body.to_string()),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(historical["receipt"]["state"], "unavailable");
|
||||
assert_eq!(historical["receipt"]["policy_generation"], 1);
|
||||
Arc::make_mut(&mut Arc::get_mut(&mut state).unwrap().config)
|
||||
.nip_fi_provision_binding = binding;
|
||||
let text = body.to_string();
|
||||
let auth = fresh_upgrade_auth(
|
||||
&operator,
|
||||
&format!("http://{INGRESS_HOST}{path}"),
|
||||
"POST",
|
||||
Some(text.as_bytes()),
|
||||
);
|
||||
let first = operator_request_with_auth(
|
||||
state.clone(),
|
||||
"POST",
|
||||
path,
|
||||
Some(text.clone()),
|
||||
auth.clone(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(first.status(), StatusCode::OK);
|
||||
let replayed_signature =
|
||||
operator_request_with_auth(state.clone(), "POST", path, Some(text), auth).await;
|
||||
assert_eq!(replayed_signature.status(), StatusCode::UNAUTHORIZED);
|
||||
assert_eq!(
|
||||
replayed_signature.headers()[header::CACHE_CONTROL],
|
||||
"no-store"
|
||||
);
|
||||
}
|
||||
history.push(body.clone());
|
||||
for old in &history {
|
||||
let id = old["operation_id"].as_str().unwrap();
|
||||
@@ -125,15 +249,6 @@ mod postgres_tests {
|
||||
"superseded"
|
||||
}
|
||||
);
|
||||
let legacy = fresh_upgrade_request(
|
||||
state.clone(),
|
||||
&operator,
|
||||
"GET",
|
||||
&format!("/operator/communities/nip-fi-upgrade?operation_id={id}"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(legacy.status(), StatusCode::NOT_FOUND);
|
||||
let replay = fresh_upgrade_request(
|
||||
state.clone(),
|
||||
&operator,
|
||||
@@ -225,6 +340,87 @@ mod postgres_tests {
|
||||
)
|
||||
.await;
|
||||
assert_eq!(invalid.status(), StatusCode::BAD_REQUEST);
|
||||
// Old acceptance rows remain recoverable through the sole supported
|
||||
// endpoint, including after their target has been deleted. Their null
|
||||
// preconditions cannot match a new fenced request with the same UUID.
|
||||
for target in [*community.id.as_uuid(), Uuid::new_v4()] {
|
||||
let id = Uuid::new_v4();
|
||||
sqlx::query("INSERT INTO community_auth_upgrades
|
||||
(operation_id,target_community_id,expected_host,actor_pubkey,expected_owner_pubkey,
|
||||
expected_issuer,operator_pubkey,policy_generation) VALUES ($1,$2,$3,$4,$4,$5,$6,1)")
|
||||
.bind(id).bind(target).bind(&host).bind(owner.public_key().to_hex())
|
||||
.bind(NIP_FI_ISSUER).bind(operator.public_key().to_hex())
|
||||
.execute(state.db.pool()).await.unwrap();
|
||||
let receipt = read_json(
|
||||
fresh_upgrade_request(
|
||||
state.clone(),
|
||||
&operator,
|
||||
"GET",
|
||||
&format!("{path}?operation_id={id}"),
|
||||
None,
|
||||
)
|
||||
.await,
|
||||
)
|
||||
.await;
|
||||
assert!(receipt["receipt"]["expected_policy_generation"].is_null());
|
||||
assert!(receipt["receipt"]["expected_requires_nip_fi"].is_null());
|
||||
assert_eq!(receipt["receipt"]["requires_nip_fi"], true);
|
||||
assert_eq!(
|
||||
receipt["receipt"]["state"],
|
||||
if target == *community.id.as_uuid() {
|
||||
"superseded"
|
||||
} else {
|
||||
"unavailable"
|
||||
}
|
||||
);
|
||||
if target != *community.id.as_uuid() {
|
||||
assert!(receipt["receipt"]["current_policy_generation"].is_null());
|
||||
assert!(receipt["receipt"]["current_requires_nip_fi"].is_null());
|
||||
}
|
||||
let mut reuse = history[0].clone();
|
||||
reuse["operation_id"] = id.to_string().into();
|
||||
reuse["community_id"] = target.to_string().into();
|
||||
reuse["expected_policy_generation"] = 4.into();
|
||||
let conflict = fresh_upgrade_request(
|
||||
state.clone(),
|
||||
&operator,
|
||||
"POST",
|
||||
path,
|
||||
Some(reuse.to_string()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(conflict.status(), StatusCode::CONFLICT);
|
||||
assert_eq!(read_json(conflict).await["code"], "idempotency_conflict");
|
||||
let hidden = fresh_upgrade_request(
|
||||
state.clone(),
|
||||
&Keys::generate(),
|
||||
"GET",
|
||||
&format!("{path}?operation_id={id}"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(hidden.status(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
let current = read_json(
|
||||
fresh_upgrade_request(state.clone(), &operator, "GET", ¤t_path, None).await,
|
||||
)
|
||||
.await;
|
||||
assert!(current.get("requires_nip_fi").is_none());
|
||||
assert!(current.get("policy_generation").is_none());
|
||||
assert_eq!(current["expected_policy_generation"], 4);
|
||||
assert_eq!(current["expected_requires_nip_fi"], false);
|
||||
// The removed route has no operator-auth handler even for signed calls.
|
||||
for method in ["GET", "POST"] {
|
||||
let gone = fresh_upgrade_request(
|
||||
state.clone(),
|
||||
&operator,
|
||||
method,
|
||||
"/operator/communities/nip-fi-upgrade",
|
||||
(method == "POST").then(|| "{}".into()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(gone.status(), StatusCode::NOT_FOUND);
|
||||
}
|
||||
// The control-plane signature and allowlist remain mandatory.
|
||||
let outsider = Keys::generate();
|
||||
let response = fresh_upgrade_request(state, &outsider, "GET", ¤t_path, None).await;
|
||||
|
||||
Reference in New Issue
Block a user