Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ jobs:
-p buzz-relay \
-p buzz-test-client \
--lib \
--test nip_fi_runtime_conformance \
--test e2e_event_reminder \
--archive-file target/ci/backend-integration-tests.tar.zst
- name: Save relay artifacts cache
Expand Down Expand Up @@ -713,6 +714,20 @@ jobs:
--run-ignored all
env:
DATABASE_URL: postgres://buzz:${{ env.BUZZ_TEST_POSTGRES_PASSWORD }}@localhost:5432/buzz_identity_tests
- name: NIP-FI runtime and protected transport conformance
run: |
cargo nextest run \
--archive-file target/ci/backend-integration-tests.tar.zst \
-E 'binary(nip_fi_runtime_conformance)'
cargo nextest run \
--archive-file target/ci/backend-integration-tests.tar.zst \
-E 'package(buzz-relay) and (test(protected_media_reads_require_corporate_identity_for_get_and_head) or test(moderation_reads_require_corporate_identity_after_nip98_proof))' \
--test-threads 1 \
--run-ignored ignored-only
env:
DATABASE_URL: postgres://buzz:${{ env.BUZZ_TEST_POSTGRES_PASSWORD }}@localhost:5432/buzz
BUZZ_TEST_DATABASE_URL: postgres://buzz:${{ env.BUZZ_TEST_POSTGRES_PASSWORD }}@localhost:5432/buzz
REDIS_URL: redis://localhost:6379
- name: Workspace profile (kind:9033) gate tests
# Call-site integration for the 9033 authorization gate: open relay
# rosterless/steward transitions and the closed-relay admin/owner rule,
Expand All @@ -725,6 +740,20 @@ jobs:
--run-ignored ignored-only
env:
DATABASE_URL: postgres://buzz:${{ env.BUZZ_TEST_POSTGRES_PASSWORD }}@localhost:5432/buzz
- name: Protected Git and media authority migration tests
run: |
docker exec -e PGPASSWORD="${BUZZ_TEST_POSTGRES_PASSWORD}" buzz-postgres \
psql -U buzz -d postgres -v ON_ERROR_STOP=1 \
-c "CREATE DATABASE buzz_visibility_tests"
cargo nextest run \
--archive-file target/ci/backend-integration-tests.tar.zst \
-E '(package(buzz-db) and test(/protected_visibility::tests::cutover_waits/)) or (package(buzz-relay) and test(/api::media_migration::tests::populated_git_and_media_cutover/))' \
--test-threads 1 \
--run-ignored ignored-only
env:
DATABASE_URL: postgres://buzz:${{ env.BUZZ_TEST_POSTGRES_PASSWORD }}@localhost:5432/buzz_visibility_tests
BUZZ_TEST_DATABASE_URL: postgres://buzz:${{ env.BUZZ_TEST_POSTGRES_PASSWORD }}@localhost:5432/buzz_visibility_tests
REDIS_URL: redis://localhost:6379
- name: NIP-ER reminder e2e
# Feature e2e for NIP-ER (Event Reminders, kind:30300): write-path
# validation, author-only read filtering, and scheduler delivery against
Expand Down
84 changes: 84 additions & 0 deletions crates/buzz-auth/src/context/evidence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,11 @@ impl VerifiedProviderEvidence {
self.fresh_until
}

/// Sealed normalized assertion carried by this verified evidence object.
pub const fn verified_assertion(&self) -> &VerifiedFederatedAssertion {
&self.assertion
}

/// Revalidate this evidence for one exact protected request.
pub fn validate_for(
&self,
Expand Down Expand Up @@ -659,6 +664,67 @@ impl DelegationCapability {
}
}

/// Exact authority-defined identity of one verified delegated relationship.
///
/// This identifier must come from the successful delegation verifier. It is
/// deliberately distinct from owner, delegate, and identity-binding IDs so a
/// broad principal selector cannot stand in for one signed relationship.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct DelegatedRelationshipId(Uuid);

impl DelegatedRelationshipId {
pub(crate) fn new(value: Uuid) -> Result<Self, AuthContextError> {
if value.is_nil() {
return Err(AuthContextError::InvalidDelegatedRelationshipId);
}
Ok(Self(value))
}

/// Opaque stable relationship identifier.
pub const fn as_uuid(self) -> Uuid {
self.0
}
}

impl fmt::Debug for DelegatedRelationshipId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("DelegatedRelationshipId")
.field(&"[redacted]")
.finish()
}
}

/// Monotonic authority revision for one delegated relationship.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DelegatedRelationshipRevision(u64);

impl DelegatedRelationshipRevision {
/// Initial revision of an immutable signed relationship issuance.
pub const INITIAL: Self = Self(1);

pub(crate) fn new(value: u64) -> Result<Self, AuthContextError> {
if value == 0 {
return Err(AuthContextError::InvalidDelegatedRelationshipRevision);
}
Ok(Self(value))
}

/// Positive monotonic relationship revision.
pub const fn get(self) -> u64 {
self.0
}
}

impl fmt::Debug for DelegatedRelationshipRevision {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("DelegatedRelationshipRevision")
.field(&"[redacted]")
.finish()
}
}

/// Transport-wide delegation from a bound owner to the authenticated key.
///
/// A verifier may construct this only after proving the capability authorizes
Expand All @@ -671,6 +737,8 @@ impl DelegationCapability {
pub struct VerifiedTransportDelegation {
owner_pubkey: PublicKey,
delegate_pubkey: PublicKey,
relationship_id: DelegatedRelationshipId,
relationship_revision: DelegatedRelationshipRevision,
capability: DelegationCapability,
expires_at: Option<DelegationExpiry>,
}
Expand All @@ -681,6 +749,8 @@ impl fmt::Debug for VerifiedTransportDelegation {
.debug_struct("VerifiedTransportDelegation")
.field("owner_pubkey", &"[redacted]")
.field("delegate_pubkey", &"[redacted]")
.field("relationship_id", &"[redacted]")
.field("relationship_revision", &"[redacted]")
.field("capability", &"[redacted]")
.field("expires_at", &"[redacted]")
.finish()
Expand All @@ -693,6 +763,8 @@ impl VerifiedTransportDelegation {
pub(crate) fn new_unrestricted(
owner_pubkey: PublicKey,
delegate_pubkey: PublicKey,
relationship_id: Uuid,
relationship_revision: u64,
expires_at: Option<DelegationExpiry>,
) -> Result<Self, AuthContextError> {
if owner_pubkey == delegate_pubkey {
Expand All @@ -701,6 +773,8 @@ impl VerifiedTransportDelegation {
Ok(Self {
owner_pubkey,
delegate_pubkey,
relationship_id: DelegatedRelationshipId::new(relationship_id)?,
relationship_revision: DelegatedRelationshipRevision::new(relationship_revision)?,
capability: DelegationCapability::TransportWide,
expires_at,
})
Expand All @@ -716,6 +790,16 @@ impl VerifiedTransportDelegation {
self.delegate_pubkey
}

/// Exact verifier-defined delegated-relationship identity.
pub const fn relationship_id(&self) -> DelegatedRelationshipId {
self.relationship_id
}

/// Exact monotonic revision of the delegated relationship.
pub const fn relationship_revision(&self) -> DelegatedRelationshipRevision {
self.relationship_revision
}

/// Verified capability scope.
pub const fn capability(&self) -> DelegationCapability {
self.capability
Expand Down
11 changes: 6 additions & 5 deletions crates/buzz-auth/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,12 @@ pub use binding::{
};
pub use evidence::{
AdmissionExpiry, AssertionExpiry, AssertionNotBefore, AssertionTransport, AuthMethod,
AuthTransport, AuthorizedCommunityAccess, DelegationCapability, DelegationExpiry,
FederatedPrincipal, NostrAuthority, ProviderEvidenceValidationError,
VerifiedFederatedAssertion, VerifiedKeyAttestation, VerifiedNostrProof,
VerifiedOperationBinding, VerifiedOperationBindingKind, VerifiedOwnerAdmission,
VerifiedProviderEvidence, VerifiedTransportDelegation,
AuthTransport, AuthorizedCommunityAccess, DelegatedRelationshipId,
DelegatedRelationshipRevision, DelegationCapability, DelegationExpiry, FederatedPrincipal,
NostrAuthority, ProviderEvidenceValidationError, VerifiedFederatedAssertion,
VerifiedKeyAttestation, VerifiedNostrProof, VerifiedOperationBinding,
VerifiedOperationBindingKind, VerifiedOwnerAdmission, VerifiedProviderEvidence,
VerifiedTransportDelegation,
};
pub use reason::{AuthContextError, AuthorizationReason};

Expand Down
10 changes: 10 additions & 0 deletions crates/buzz-auth/src/context/reason.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ pub enum AuthContextError {
/// Delegation expiry was not a valid Unix timestamp.
#[error("delegation expiry must be greater than zero")]
InvalidDelegationExpiry,
/// Delegated-relationship identity was the nil UUID.
#[error("delegated relationship identifier must not be nil")]
InvalidDelegatedRelationshipId,
/// Delegated-relationship revision was zero.
#[error("delegated relationship revision must be greater than zero")]
InvalidDelegatedRelationshipRevision,
/// Admission expiry was not a valid Unix timestamp.
#[error("admission expiry must be greater than zero")]
InvalidAdmissionExpiry,
Expand Down Expand Up @@ -198,6 +204,10 @@ impl AuthContextError {
Self::InvalidFederatedPolicyInterval => "federated_policy_invalid_interval",
Self::InvalidAssertionExpiry => "federated_assertion_invalid_expiry",
Self::InvalidDelegationExpiry => "delegation_invalid_expiry",
Self::InvalidDelegatedRelationshipId => "delegation_invalid_relationship_id",
Self::InvalidDelegatedRelationshipRevision => {
"delegation_invalid_relationship_revision"
}
Self::InvalidAdmissionExpiry => "owner_admission_invalid_expiry",
Self::AssertionExpired => "federated_assertion_expired",
Self::BindingExpired => "federated_binding_expired",
Expand Down
17 changes: 14 additions & 3 deletions crates/buzz-auth/src/context/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ fn input_with_delegation_expiry(
VerifiedTransportDelegation::new_unrestricted(
owner_pubkey,
actor_pubkey,
Uuid::from_u128(0x601),
1,
Some(
DelegationExpiry::new(delegation_expiry)
.expect("synthetic delegation expiry is valid"),
Expand Down Expand Up @@ -644,6 +646,8 @@ fn verified_nostr_proof_requires_the_authenticated_delegate() {
let delegation = VerifiedTransportDelegation::new_unrestricted(
owner.public_key(),
other_delegate.public_key(),
Uuid::from_u128(0x602),
1,
None,
)
.expect("synthetic owner and delegate are distinct");
Expand Down Expand Up @@ -1856,9 +1860,14 @@ fn nostr_only_authorization_may_preserve_a_verified_owner() {
#[test]
fn transport_delegation_rejects_self_reference() {
let actor = Keys::generate();
let error =
VerifiedTransportDelegation::new_unrestricted(actor.public_key(), actor.public_key(), None)
.expect_err("an actor cannot be its own verified owner");
let error = VerifiedTransportDelegation::new_unrestricted(
actor.public_key(),
actor.public_key(),
Uuid::from_u128(0x603),
1,
None,
)
.expect_err("an actor cannot be its own verified owner");

assert_eq!(error, AuthContextError::SelfDelegation);
}
Expand All @@ -1870,6 +1879,8 @@ fn transport_delegation_is_explicitly_transport_wide() {
let delegation = VerifiedTransportDelegation::new_unrestricted(
owner.public_key(),
delegate.public_key(),
Uuid::from_u128(0x604),
1,
None,
)
.expect("synthetic owner and delegate are distinct");
Expand Down
12 changes: 12 additions & 0 deletions crates/buzz-auth/src/evidence_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ pub enum ActiveBindingResolution {
pub struct VerifiedDelegationOutput {
owner_pubkey: PublicKey,
delegate_pubkey: PublicKey,
relationship_id: Uuid,
relationship_revision: u64,
expires_at: Option<u64>,
transport_wide: bool,
}
Expand All @@ -52,12 +54,16 @@ impl VerifiedDelegationOutput {
pub const fn from_workspace_verifier(
owner_pubkey: PublicKey,
delegate_pubkey: PublicKey,
relationship_id: Uuid,
relationship_revision: u64,
expires_at: Option<u64>,
transport_wide: bool,
) -> Self {
Self {
owner_pubkey,
delegate_pubkey,
relationship_id,
relationship_revision,
expires_at,
transport_wide,
}
Expand Down Expand Up @@ -288,6 +294,8 @@ impl VerifiedEvidenceAdapter {
VerifiedTransportDelegation::new_unrestricted(
output.owner_pubkey,
output.delegate_pubkey,
output.relationship_id,
output.relationship_revision,
expires_at,
)
.map_err(Into::into)
Expand Down Expand Up @@ -650,6 +658,8 @@ mod tests {
Some(VerifiedDelegationOutput::from_workspace_verifier(
owner.public_key(),
actor.public_key(),
Uuid::from_u128(0x701),
1,
None,
true,
)),
Expand Down Expand Up @@ -694,6 +704,8 @@ mod tests {
Some(VerifiedDelegationOutput::from_workspace_verifier(
owner.public_key(),
Keys::generate().public_key(),
Uuid::from_u128(0x702),
1,
None,
true,
)),
Expand Down
2 changes: 2 additions & 0 deletions crates/buzz-auth/src/finalization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,8 @@ mod tests {
VerifiedTransportDelegation::new_unrestricted(
owner,
delegate,
Uuid::from_u128(0x401),
1,
Some(
DelegationExpiry::new(1_050).expect("synthetic delegation expiry is valid"),
),
Expand Down
14 changes: 7 additions & 7 deletions crates/buzz-auth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ pub use context::{
AuthContextVersion, AuthMethod, AuthTransport, AuthorityAdapterError, AuthorityAdapterFuture,
AuthorizationReason, AuthorizedCommunityAccess, BindingResolutionRequest, BindingSource,
BindingVersion, CapabilityFinalizationSeal, CurrentPolicyRequest, CurrentPolicyResolutionSink,
DelegationCapability, DelegationExpiry, DirectBindingResolutionSink, EnrollmentMode,
ExistingBindingResolutionSink, FederatedAuthorityAdapter, FederatedAuthorization,
FederatedIdentityRequirement, FederatedPrincipal, NostrAuthority,
ProviderEvidenceValidationError, ResolvedFederatedPolicy, VerifiedFederatedAssertion,
VerifiedKeyAttestation, VerifiedNostrProof, VerifiedOperationBinding,
VerifiedOperationBindingKind, VerifiedOwnerAdmission, VerifiedProviderEvidence,
VerifiedTransportDelegation, VersionedBindingRef,
DelegatedRelationshipId, DelegatedRelationshipRevision, DelegationCapability, DelegationExpiry,
DirectBindingResolutionSink, EnrollmentMode, ExistingBindingResolutionSink,
FederatedAuthorityAdapter, FederatedAuthorization, FederatedIdentityRequirement,
FederatedPrincipal, NostrAuthority, ProviderEvidenceValidationError, ResolvedFederatedPolicy,
VerifiedFederatedAssertion, VerifiedKeyAttestation, VerifiedNostrProof,
VerifiedOperationBinding, VerifiedOperationBindingKind, VerifiedOwnerAdmission,
VerifiedProviderEvidence, VerifiedTransportDelegation, VersionedBindingRef,
};
pub use error::AuthError;
pub use evidence_adapter::{
Expand Down
6 changes: 6 additions & 0 deletions crates/buzz-auth/src/provider/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,8 @@ fn delegated_proof(actor: &Keys, owner: &Keys, expiry: u64) -> VerifiedNostrProo
let delegation = VerifiedTransportDelegation::new_unrestricted(
owner.public_key(),
actor.public_key(),
Uuid::from_u128(0x501),
1,
Some(DelegationExpiry::new(expiry).expect("synthetic delegation expiry is valid")),
)
.expect("synthetic delegation is valid");
Expand Down Expand Up @@ -2216,6 +2218,8 @@ fn request_construction_rejects_mismatched_verified_evidence() {
let delegation = VerifiedTransportDelegation::new_unrestricted(
owner.public_key(),
actor.public_key(),
Uuid::from_u128(0x502),
1,
Some(DelegationExpiry::new(NOW + 20).expect("synthetic expiry is valid")),
)
.expect("synthetic delegation is valid");
Expand Down Expand Up @@ -2273,6 +2277,8 @@ fn request_construction_rejects_mismatched_verified_evidence() {
let expired_delegation = VerifiedTransportDelegation::new_unrestricted(
owner.public_key(),
actor.public_key(),
Uuid::from_u128(0x503),
1,
Some(DelegationExpiry::new(NOW).expect("synthetic expiry is valid")),
)
.expect("synthetic delegation is valid");
Expand Down
Loading
Loading