authorization.rs (21599B)
1 #![forbid(unsafe_code)] 2 3 use crate::{RadrootsActorContext, RadrootsAuthorityError, RadrootsEventSigner}; 4 use radroots_events::contract::{RadrootsEventContract, event_contract}; 5 #[cfg(test)] 6 use radroots_events::draft::RadrootsSignedNostrEventParts; 7 use radroots_events::draft::{ 8 RadrootsDraftError, RadrootsFrozenEventDraft, RadrootsSignedNostrEvent, 9 validate_signed_nostr_event_matches_draft, 10 }; 11 12 #[cfg(not(feature = "std"))] 13 use alloc::{borrow::ToOwned, string::ToString}; 14 #[cfg(feature = "std")] 15 use std::{borrow::ToOwned, string::ToString}; 16 17 pub fn authorize_actor_for_contract( 18 actor: &RadrootsActorContext, 19 contract: &RadrootsEventContract, 20 ) -> Result<(), RadrootsAuthorityError> { 21 if actor.satisfies(contract.author_role) { 22 Ok(()) 23 } else { 24 Err(RadrootsAuthorityError::ActorRoleUnsatisfied { 25 contract_id: contract.id.to_owned(), 26 required_role: contract.author_role, 27 }) 28 } 29 } 30 31 pub fn authorize_actor_for_draft( 32 actor: &RadrootsActorContext, 33 draft: &RadrootsFrozenEventDraft, 34 ) -> Result<&'static RadrootsEventContract, RadrootsAuthorityError> { 35 let contract = event_contract(draft.contract_id.as_str()).ok_or_else(|| { 36 RadrootsAuthorityError::UnknownContract { 37 contract_id: draft.contract_id.clone(), 38 } 39 })?; 40 if contract.kind != draft.kind { 41 return Err(RadrootsAuthorityError::DraftKindMismatch { 42 contract_id: draft.contract_id.clone(), 43 expected_kind: contract.kind, 44 actual_kind: draft.kind, 45 }); 46 } 47 authorize_actor_for_contract(actor, contract)?; 48 if actor.pubkey().as_str() != draft.expected_pubkey.as_str() { 49 return Err(RadrootsAuthorityError::ActorPubkeyMismatch { 50 expected_pubkey: draft.expected_pubkey.clone(), 51 actor_pubkey: actor.pubkey().as_str().to_owned(), 52 }); 53 } 54 Ok(contract) 55 } 56 57 pub fn authorize_signer_for_draft<S>( 58 signer: &S, 59 draft: &RadrootsFrozenEventDraft, 60 ) -> Result<(), RadrootsAuthorityError> 61 where 62 S: RadrootsEventSigner + ?Sized, 63 { 64 if signer.pubkey().as_str() == draft.expected_pubkey.as_str() { 65 Ok(()) 66 } else { 67 Err(RadrootsAuthorityError::SignerPubkeyMismatch { 68 expected_pubkey: draft.expected_pubkey.clone(), 69 signer_pubkey: signer.pubkey().as_str().to_owned(), 70 }) 71 } 72 } 73 74 pub fn sign_authorized_draft<S>( 75 actor: &RadrootsActorContext, 76 signer: &S, 77 draft: &RadrootsFrozenEventDraft, 78 ) -> Result<RadrootsSignedNostrEvent, RadrootsAuthorityError> 79 where 80 S: RadrootsEventSigner + ?Sized, 81 { 82 authorize_actor_for_draft(actor, draft)?; 83 authorize_signer_for_draft(signer, draft)?; 84 let signed_event = signer.sign_frozen_draft(draft)?; 85 validate_signed_event_matches_draft(&signed_event, draft)?; 86 Ok(signed_event) 87 } 88 89 pub fn validate_signed_event_matches_draft( 90 signed_event: &RadrootsSignedNostrEvent, 91 draft: &RadrootsFrozenEventDraft, 92 ) -> Result<(), RadrootsAuthorityError> { 93 validate_signed_nostr_event_matches_draft(signed_event, draft) 94 .map_err(authority_error_from_draft_validation) 95 } 96 97 fn authority_error_from_draft_validation(error: RadrootsDraftError) -> RadrootsAuthorityError { 98 match error { 99 RadrootsDraftError::SignedEventPubkeyMismatch { 100 expected_pubkey, 101 actual_pubkey, 102 } => RadrootsAuthorityError::SignedEventPubkeyMismatch { 103 expected_pubkey, 104 actual_pubkey, 105 }, 106 RadrootsDraftError::SignedEventIdMismatch { 107 expected_event_id, 108 actual_event_id, 109 } => RadrootsAuthorityError::SignedEventIdMismatch { 110 expected_event_id, 111 actual_event_id, 112 }, 113 RadrootsDraftError::SignedEventCreatedAtMismatch { 114 expected_created_at, 115 actual_created_at, 116 } => RadrootsAuthorityError::SignedEventCreatedAtMismatch { 117 expected_created_at, 118 actual_created_at, 119 }, 120 RadrootsDraftError::SignedEventKindMismatch { 121 expected_kind, 122 actual_kind, 123 } => RadrootsAuthorityError::SignedEventKindMismatch { 124 expected_kind, 125 actual_kind, 126 }, 127 RadrootsDraftError::SignedEventTagsMismatch { 128 expected_len, 129 actual_len, 130 } => RadrootsAuthorityError::SignedEventTagsMismatch { 131 expected_len, 132 actual_len, 133 }, 134 RadrootsDraftError::SignedEventContentMismatch { 135 expected_len, 136 actual_len, 137 } => RadrootsAuthorityError::SignedEventContentMismatch { 138 expected_len, 139 actual_len, 140 }, 141 RadrootsDraftError::SignedEventComputedIdMismatch { 142 expected_event_id, 143 computed_event_id, 144 } => RadrootsAuthorityError::SignedEventComputedIdMismatch { 145 expected_event_id, 146 computed_event_id, 147 }, 148 error => RadrootsAuthorityError::SignedEventComputedIdInvalid { 149 message: error.to_string(), 150 }, 151 } 152 } 153 154 #[cfg(test)] 155 mod tests { 156 use super::*; 157 use crate::RadrootsSignerError; 158 use radroots_events::contract::{RadrootsActorRole, event_contract}; 159 use radroots_events::ids::RadrootsPublicKey; 160 use radroots_events::kinds::{KIND_LISTING, KIND_ORDER_REQUEST, KIND_POST}; 161 162 fn hex_64(character: char) -> String { 163 std::iter::repeat_n(character, 64).collect() 164 } 165 166 fn hex_128(character: char) -> String { 167 std::iter::repeat_n(character, 128).collect() 168 } 169 170 fn seller_actor(pubkey: &str) -> RadrootsActorContext { 171 RadrootsActorContext::explicit_pubkey(pubkey, [RadrootsActorRole::Seller]).expect("seller") 172 } 173 174 fn buyer_actor(pubkey: &str) -> RadrootsActorContext { 175 RadrootsActorContext::explicit_pubkey(pubkey, [RadrootsActorRole::Buyer]).expect("buyer") 176 } 177 178 fn listing_draft(pubkey: &str) -> RadrootsFrozenEventDraft { 179 RadrootsFrozenEventDraft::new( 180 "radroots.listing.published.v1", 181 KIND_LISTING, 182 1_700_000_000, 183 vec![vec!["d".to_owned(), "listing-a".to_owned()]], 184 "{}", 185 pubkey, 186 ) 187 .expect("listing draft") 188 } 189 190 #[derive(Default)] 191 struct SignedEventOverrides { 192 event_id: Option<String>, 193 created_at: Option<u32>, 194 kind: Option<u32>, 195 tags: Option<Vec<Vec<String>>>, 196 content: Option<String>, 197 } 198 199 struct StaticSigner { 200 pubkey: RadrootsPublicKey, 201 overrides: SignedEventOverrides, 202 } 203 204 impl StaticSigner { 205 fn new(pubkey: &str) -> Self { 206 Self { 207 pubkey: RadrootsPublicKey::parse(pubkey).expect("pubkey"), 208 overrides: SignedEventOverrides::default(), 209 } 210 } 211 212 fn with_event_id(pubkey: &str, event_id: String) -> Self { 213 Self::with_overrides( 214 pubkey, 215 SignedEventOverrides { 216 event_id: Some(event_id), 217 ..SignedEventOverrides::default() 218 }, 219 ) 220 } 221 222 fn with_overrides(pubkey: &str, overrides: SignedEventOverrides) -> Self { 223 Self { 224 pubkey: RadrootsPublicKey::parse(pubkey).expect("pubkey"), 225 overrides, 226 } 227 } 228 } 229 230 impl RadrootsEventSigner for StaticSigner { 231 fn pubkey(&self) -> &RadrootsPublicKey { 232 &self.pubkey 233 } 234 235 fn sign_frozen_draft( 236 &self, 237 draft: &RadrootsFrozenEventDraft, 238 ) -> Result<RadrootsSignedNostrEvent, RadrootsSignerError> { 239 RadrootsSignedNostrEvent::new(RadrootsSignedNostrEventParts { 240 id: self 241 .overrides 242 .event_id 243 .as_deref() 244 .unwrap_or(draft.expected_event_id.as_str()) 245 .to_owned(), 246 pubkey: self.pubkey.to_string(), 247 created_at: self.overrides.created_at.unwrap_or(draft.created_at), 248 kind: self.overrides.kind.unwrap_or(draft.kind), 249 tags: self 250 .overrides 251 .tags 252 .clone() 253 .unwrap_or_else(|| draft.tags.clone()), 254 content: self 255 .overrides 256 .content 257 .clone() 258 .unwrap_or_else(|| draft.content.clone()), 259 sig: hex_128('f'), 260 raw_json: "{}".to_owned(), 261 }) 262 .map_err(|error| RadrootsSignerError::SigningFailed { 263 message: error.to_string(), 264 }) 265 } 266 } 267 268 fn signed_event_from_draft(draft: &RadrootsFrozenEventDraft) -> RadrootsSignedNostrEvent { 269 RadrootsSignedNostrEvent::new(RadrootsSignedNostrEventParts { 270 id: draft.expected_event_id.clone(), 271 pubkey: draft.expected_pubkey.clone(), 272 created_at: draft.created_at, 273 kind: draft.kind, 274 tags: draft.tags.clone(), 275 content: draft.content.clone(), 276 sig: hex_128('f'), 277 raw_json: "{}".to_owned(), 278 }) 279 .expect("signed event") 280 } 281 282 #[test] 283 fn buyer_and_seller_contract_roles_match_current_contracts() { 284 let listing = event_contract("radroots.listing.published.v1").expect("listing contract"); 285 let order_request = event_contract("radroots.order.request.v1").expect("order contract"); 286 let order_revision_proposal = 287 event_contract("radroots.order.revision_proposal.v1").expect("revision proposal"); 288 let order_revision_decision = 289 event_contract("radroots.order.revision_decision.v1").expect("revision decision"); 290 let seller = seller_actor(hex_64('a').as_str()); 291 let buyer = buyer_actor(hex_64('b').as_str()); 292 293 assert_eq!(listing.author_role, RadrootsActorRole::Seller); 294 assert!(authorize_actor_for_contract(&seller, listing).is_ok()); 295 assert!(matches!( 296 authorize_actor_for_contract(&buyer, listing), 297 Err(RadrootsAuthorityError::ActorRoleUnsatisfied { .. }) 298 )); 299 assert!(authorize_actor_for_contract(&buyer, order_request).is_ok()); 300 assert!(matches!( 301 authorize_actor_for_contract(&seller, order_request), 302 Err(RadrootsAuthorityError::ActorRoleUnsatisfied { .. }) 303 )); 304 assert_eq!( 305 order_revision_proposal.author_role, 306 RadrootsActorRole::Seller 307 ); 308 assert!(authorize_actor_for_contract(&seller, order_revision_proposal).is_ok()); 309 assert!(matches!( 310 authorize_actor_for_contract(&buyer, order_revision_proposal), 311 Err(RadrootsAuthorityError::ActorRoleUnsatisfied { .. }) 312 )); 313 assert_eq!( 314 order_revision_decision.author_role, 315 RadrootsActorRole::Buyer 316 ); 317 assert!(authorize_actor_for_contract(&buyer, order_revision_decision).is_ok()); 318 assert!(matches!( 319 authorize_actor_for_contract(&seller, order_revision_decision), 320 Err(RadrootsAuthorityError::ActorRoleUnsatisfied { .. }) 321 )); 322 } 323 324 #[test] 325 fn actor_pubkey_mismatch_fails() { 326 let draft = listing_draft(hex_64('a').as_str()); 327 let actor = seller_actor(hex_64('b').as_str()); 328 329 assert!(matches!( 330 authorize_actor_for_draft(&actor, &draft), 331 Err(RadrootsAuthorityError::ActorPubkeyMismatch { .. }) 332 )); 333 } 334 335 #[test] 336 fn signer_pubkey_mismatch_fails() { 337 let draft = listing_draft(hex_64('a').as_str()); 338 let signer = StaticSigner::new(hex_64('b').as_str()); 339 340 assert!(matches!( 341 authorize_signer_for_draft(&signer, &draft), 342 Err(RadrootsAuthorityError::SignerPubkeyMismatch { .. }) 343 )); 344 } 345 346 #[test] 347 fn unknown_contract_and_kind_mismatch_fail() { 348 let actor = seller_actor(hex_64('a').as_str()); 349 let unknown = RadrootsFrozenEventDraft { 350 contract_id: "radroots.unknown.v1".to_owned(), 351 contract_registry_version: 1, 352 kind: KIND_LISTING, 353 created_at: 1_700_000_000, 354 tags: Vec::new(), 355 content: "{}".to_owned(), 356 expected_pubkey: hex_64('a'), 357 expected_event_id: hex_64('e'), 358 }; 359 assert!(matches!( 360 authorize_actor_for_draft(&actor, &unknown), 361 Err(RadrootsAuthorityError::UnknownContract { .. }) 362 )); 363 364 let wrong_kind = RadrootsFrozenEventDraft { 365 contract_id: "radroots.listing.published.v1".to_owned(), 366 contract_registry_version: 1, 367 kind: KIND_POST, 368 created_at: 1_700_000_000, 369 tags: Vec::new(), 370 content: "{}".to_owned(), 371 expected_pubkey: hex_64('a'), 372 expected_event_id: hex_64('e'), 373 }; 374 assert!(matches!( 375 authorize_actor_for_draft(&actor, &wrong_kind), 376 Err(RadrootsAuthorityError::DraftKindMismatch { 377 expected_kind: KIND_LISTING, 378 actual_kind: KIND_POST, 379 .. 380 }) 381 )); 382 } 383 384 #[test] 385 fn signed_event_id_mismatch_fails() { 386 let pubkey = hex_64('a'); 387 let draft = listing_draft(pubkey.as_str()); 388 let actor = seller_actor(pubkey.as_str()); 389 let signer = StaticSigner::with_event_id(pubkey.as_str(), hex_64('e')); 390 391 assert!(matches!( 392 sign_authorized_draft(&actor, &signer, &draft), 393 Err(RadrootsAuthorityError::SignedEventIdMismatch { .. }) 394 )); 395 } 396 397 #[test] 398 fn signed_event_created_at_mismatch_fails() { 399 let pubkey = hex_64('a'); 400 let draft = listing_draft(pubkey.as_str()); 401 let actor = seller_actor(pubkey.as_str()); 402 let signer = StaticSigner::with_overrides( 403 pubkey.as_str(), 404 SignedEventOverrides { 405 created_at: Some(draft.created_at + 1), 406 ..SignedEventOverrides::default() 407 }, 408 ); 409 410 assert!(matches!( 411 sign_authorized_draft(&actor, &signer, &draft), 412 Err(RadrootsAuthorityError::SignedEventCreatedAtMismatch { .. }) 413 )); 414 } 415 416 #[test] 417 fn signed_event_kind_mismatch_fails() { 418 let pubkey = hex_64('a'); 419 let draft = listing_draft(pubkey.as_str()); 420 let actor = seller_actor(pubkey.as_str()); 421 let signer = StaticSigner::with_overrides( 422 pubkey.as_str(), 423 SignedEventOverrides { 424 kind: Some(KIND_POST), 425 ..SignedEventOverrides::default() 426 }, 427 ); 428 429 assert!(matches!( 430 sign_authorized_draft(&actor, &signer, &draft), 431 Err(RadrootsAuthorityError::SignedEventKindMismatch { 432 expected_kind: KIND_LISTING, 433 actual_kind: KIND_POST 434 }) 435 )); 436 } 437 438 #[test] 439 fn signed_event_tags_mismatch_fails() { 440 let pubkey = hex_64('a'); 441 let draft = listing_draft(pubkey.as_str()); 442 let actor = seller_actor(pubkey.as_str()); 443 let signer = StaticSigner::with_overrides( 444 pubkey.as_str(), 445 SignedEventOverrides { 446 tags: Some(vec![vec!["d".to_owned(), "listing-b".to_owned()]]), 447 ..SignedEventOverrides::default() 448 }, 449 ); 450 451 let error = sign_authorized_draft(&actor, &signer, &draft).unwrap_err(); 452 453 assert_eq!( 454 error, 455 RadrootsAuthorityError::SignedEventTagsMismatch { 456 expected_len: 1, 457 actual_len: 1 458 } 459 ); 460 assert!(!format!("{error:?}").contains("listing-b")); 461 assert!(!error.to_string().contains("listing-b")); 462 } 463 464 #[test] 465 fn signed_event_content_mismatch_fails() { 466 let pubkey = hex_64('a'); 467 let draft = listing_draft(pubkey.as_str()); 468 let actor = seller_actor(pubkey.as_str()); 469 let signer = StaticSigner::with_overrides( 470 pubkey.as_str(), 471 SignedEventOverrides { 472 content: Some("{\"changed\":true}".to_owned()), 473 ..SignedEventOverrides::default() 474 }, 475 ); 476 477 let error = sign_authorized_draft(&actor, &signer, &draft).unwrap_err(); 478 479 assert_eq!( 480 error, 481 RadrootsAuthorityError::SignedEventContentMismatch { 482 expected_len: 2, 483 actual_len: 16 484 } 485 ); 486 assert!(!format!("{error:?}").contains("changed")); 487 assert!(!error.to_string().contains("changed")); 488 } 489 490 #[test] 491 fn signed_event_exactly_matching_draft_passes() { 492 let pubkey = hex_64('a'); 493 let draft = listing_draft(pubkey.as_str()); 494 let signed = signed_event_from_draft(&draft); 495 496 validate_signed_event_matches_draft(&signed, &draft).expect("signed event matches draft"); 497 } 498 499 #[test] 500 fn signed_event_pubkey_mismatch_fails() { 501 let pubkey = hex_64('a'); 502 let draft = listing_draft(pubkey.as_str()); 503 let signed = RadrootsSignedNostrEvent::new(RadrootsSignedNostrEventParts { 504 id: draft.expected_event_id.clone(), 505 pubkey: hex_64('b'), 506 created_at: draft.created_at, 507 kind: draft.kind, 508 tags: draft.tags.clone(), 509 content: draft.content.clone(), 510 sig: hex_128('f'), 511 raw_json: "{}".to_owned(), 512 }) 513 .expect("signed event"); 514 515 assert!(matches!( 516 validate_signed_event_matches_draft(&signed, &draft), 517 Err(RadrootsAuthorityError::SignedEventPubkeyMismatch { .. }) 518 )); 519 } 520 521 #[test] 522 fn draft_validation_fallback_errors_map_to_computed_id_invalid() { 523 let error = authority_error_from_draft_validation(RadrootsDraftError::UnknownContract( 524 "radroots.unknown.v1".to_owned(), 525 )); 526 527 assert!(matches!( 528 error, 529 RadrootsAuthorityError::SignedEventComputedIdInvalid { .. } 530 )); 531 } 532 533 #[test] 534 fn signed_event_computed_id_mismatch_fails() { 535 let pubkey = hex_64('a'); 536 let inconsistent_draft = RadrootsFrozenEventDraft { 537 contract_id: "radroots.listing.published.v1".to_owned(), 538 contract_registry_version: 1, 539 kind: KIND_LISTING, 540 created_at: 1_700_000_000, 541 tags: vec![vec!["d".to_owned(), "listing-a".to_owned()]], 542 content: "{}".to_owned(), 543 expected_pubkey: pubkey, 544 expected_event_id: hex_64('e'), 545 }; 546 let signed = signed_event_from_draft(&inconsistent_draft); 547 548 assert!(matches!( 549 validate_signed_event_matches_draft(&signed, &inconsistent_draft), 550 Err(RadrootsAuthorityError::SignedEventComputedIdMismatch { .. }) 551 )); 552 } 553 554 #[test] 555 fn sign_authorized_draft_calls_full_integrity_check() { 556 let pubkey = hex_64('a'); 557 let inconsistent_draft = RadrootsFrozenEventDraft { 558 contract_id: "radroots.listing.published.v1".to_owned(), 559 contract_registry_version: 1, 560 kind: KIND_LISTING, 561 created_at: 1_700_000_000, 562 tags: vec![vec!["d".to_owned(), "listing-a".to_owned()]], 563 content: "{}".to_owned(), 564 expected_pubkey: pubkey.clone(), 565 expected_event_id: hex_64('e'), 566 }; 567 let actor = seller_actor(pubkey.as_str()); 568 let signer = StaticSigner::new(pubkey.as_str()); 569 570 assert!(matches!( 571 sign_authorized_draft(&actor, &signer, &inconsistent_draft), 572 Err(RadrootsAuthorityError::SignedEventComputedIdMismatch { .. }) 573 )); 574 } 575 576 #[test] 577 fn static_signer_maps_invalid_signed_event_parts() { 578 let pubkey = hex_64('a'); 579 let mut draft = listing_draft(pubkey.as_str()); 580 draft.expected_event_id = "bad-id".to_owned(); 581 let signer = StaticSigner::new(pubkey.as_str()); 582 583 assert!(matches!( 584 signer.sign_frozen_draft(&draft), 585 Err(RadrootsSignerError::SigningFailed { .. }) 586 )); 587 } 588 589 #[test] 590 fn authorized_actor_and_signer_return_signed_event() { 591 let pubkey = hex_64('a'); 592 let draft = listing_draft(pubkey.as_str()); 593 let actor = seller_actor(pubkey.as_str()); 594 let signer = StaticSigner::new(pubkey.as_str()); 595 596 let signed = sign_authorized_draft(&actor, &signer, &draft).expect("signed"); 597 598 assert_eq!(signed.id, draft.expected_event_id); 599 assert_eq!(signed.pubkey, draft.expected_pubkey); 600 assert_eq!(signed.kind, KIND_LISTING); 601 } 602 603 #[test] 604 fn order_request_draft_requires_buyer_role() { 605 let pubkey = hex_64('a'); 606 let draft = RadrootsFrozenEventDraft::new( 607 "radroots.order.request.v1", 608 KIND_ORDER_REQUEST, 609 1_700_000_000, 610 Vec::new(), 611 "{}", 612 pubkey.as_str(), 613 ) 614 .expect("order draft"); 615 let buyer = buyer_actor(pubkey.as_str()); 616 let seller = seller_actor(pubkey.as_str()); 617 618 assert!(authorize_actor_for_draft(&buyer, &draft).is_ok()); 619 assert!(matches!( 620 authorize_actor_for_draft(&seller, &draft), 621 Err(RadrootsAuthorityError::ActorRoleUnsatisfied { .. }) 622 )); 623 } 624 }