Skip to main content

bsv_wallet_toolbox/permissions/
mod.rs

1//! Permission management for wallet operations.
2//!
3//! Provides the `WalletPermissionsManager` which wraps an inner
4//! `WalletInterface` implementation and intercepts calls to enforce
5//! permission checks based on originator identity.
6
7/// BRC-114 action time label parsing utilities.
8pub mod brc114;
9/// Permission caching with concurrent request deduplication.
10pub mod cache;
11/// Async callback trait for permission request events.
12pub mod callbacks;
13/// Permission manager configuration flags.
14pub mod config;
15/// Permission checking functions (ensure_*).
16pub mod ensure;
17/// Metadata encryption/decryption for wallet descriptions.
18pub mod metadata;
19/// Originator normalization and lookup utilities.
20pub mod originator;
21/// P-Module delegation trait for BRC-98/99 schemes.
22pub mod p_module;
23/// Permission token CRUD operations via inner wallet.
24pub mod token_crud;
25/// Permission types, tokens, and request/response structs.
26pub mod types;
27
28use std::collections::HashMap;
29use std::sync::Arc;
30
31use async_trait::async_trait;
32use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
33use base64::Engine as _;
34
35use bsv::wallet::interfaces::{
36    AbortActionArgs, AbortActionResult, AcquireCertificateArgs, AuthenticatedResult, Certificate,
37    CreateActionArgs, CreateActionResult, CreateHmacArgs, CreateHmacResult, CreateSignatureArgs,
38    CreateSignatureResult, DecryptArgs, DecryptResult, DiscoverByAttributesArgs,
39    DiscoverByIdentityKeyArgs, DiscoverCertificatesResult, EncryptArgs, EncryptResult,
40    GetHeaderArgs, GetHeaderResult, GetHeightResult, GetNetworkResult, GetPublicKeyArgs,
41    GetPublicKeyResult, GetVersionResult, InternalizeActionArgs, InternalizeActionResult,
42    ListActionsArgs, ListActionsResult, ListCertificatesArgs, ListCertificatesResult,
43    ListOutputsArgs, ListOutputsResult, ProveCertificateArgs, ProveCertificateResult,
44    RelinquishCertificateArgs, RelinquishCertificateResult, RelinquishOutputArgs,
45    RelinquishOutputResult, RevealCounterpartyKeyLinkageArgs, RevealCounterpartyKeyLinkageResult,
46    RevealSpecificKeyLinkageArgs, RevealSpecificKeyLinkageResult, SignActionArgs, SignActionResult,
47    VerifyHmacArgs, VerifyHmacResult, VerifySignatureArgs, VerifySignatureResult, WalletInterface,
48};
49
50use config::PermissionsManagerConfig;
51use p_module::PermissionsModule;
52use types::{
53    GroupedPermissions, PermissionRequest, PermissionResponse, PermissionToken, PermissionType,
54};
55
56// ---------------------------------------------------------------------------
57// WalletPermissionsManager
58// ---------------------------------------------------------------------------
59
60/// Wraps an inner `WalletInterface` with permission management.
61///
62/// Intercepts calls from external applications (identified by originators),
63/// checks if the request is allowed, and proxies the actual call to the
64/// underlying wallet. The admin originator bypasses all permission checks.
65pub struct WalletPermissionsManager {
66    /// The wrapped inner wallet.
67    inner: Arc<dyn WalletInterface + Send + Sync>,
68    /// The admin originator domain (normalized). Bypasses all checks.
69    admin_originator: String,
70    /// Configuration flags controlling which permission checks are enforced.
71    config: PermissionsManagerConfig,
72    /// Registered P-Module handlers, keyed by scheme ID.
73    permission_modules: HashMap<String, Arc<dyn PermissionsModule>>,
74    /// Optional async callbacks for permission request events.
75    callbacks: Option<Arc<dyn callbacks::PermissionCallbacks>>,
76    /// Permission cache for manifest entries, recent grants, and deduplication.
77    cache: cache::PermissionCache,
78}
79
80impl WalletPermissionsManager {
81    /// Create a new `WalletPermissionsManager` wrapping the given wallet.
82    ///
83    /// The `admin_originator` domain (normalized) will bypass all permission
84    /// checks for all methods.
85    pub fn new(
86        inner: Arc<dyn WalletInterface + Send + Sync>,
87        admin_originator: String,
88        config: PermissionsManagerConfig,
89    ) -> Self {
90        let normalized_admin = originator::normalize_originator(&admin_originator);
91        Self {
92            inner,
93            admin_originator: if normalized_admin.is_empty() {
94                admin_originator
95            } else {
96                normalized_admin
97            },
98            config,
99            permission_modules: HashMap::new(),
100            callbacks: None,
101            cache: cache::PermissionCache::new(),
102        }
103    }
104
105    /// Check if the given originator is the admin.
106    pub(crate) fn is_admin(&self, originator: Option<&str>) -> bool {
107        match originator {
108            Some(o) => originator::is_admin_originator(o, &self.admin_originator),
109            None => false,
110        }
111    }
112
113    /// Register a P-Module handler for a given scheme ID.
114    ///
115    /// When a protocol or basket name starts with `"p <scheme_id>"`, the
116    /// corresponding module's `on_request`/`on_response` methods will be called.
117    pub fn register_module(&mut self, scheme_id: String, module: Arc<dyn PermissionsModule>) {
118        self.permission_modules.insert(scheme_id, module);
119    }
120
121    /// Check if a P-Module is registered for the given scheme ID.
122    pub(crate) fn has_permission_module(&self, scheme_id: &str) -> bool {
123        self.permission_modules.contains_key(scheme_id)
124    }
125
126    /// Get a reference to the configuration.
127    pub fn config(&self) -> &PermissionsManagerConfig {
128        &self.config
129    }
130
131    /// Get a reference to the inner wallet.
132    pub fn inner(&self) -> &Arc<dyn WalletInterface + Send + Sync> {
133        &self.inner
134    }
135
136    /// Get the normalized admin originator.
137    pub(crate) fn admin_originator(&self) -> String {
138        self.admin_originator.clone()
139    }
140
141    // -----------------------------------------------------------------------
142    // Callback management
143    // -----------------------------------------------------------------------
144
145    /// Register an async callback handler for permission request events.
146    pub fn bind_callback(&mut self, cbs: Arc<dyn callbacks::PermissionCallbacks>) {
147        self.callbacks = Some(cbs);
148    }
149
150    /// Remove the registered callback handler.
151    pub fn unbind_callback(&mut self) {
152        self.callbacks = None;
153    }
154
155    /// Clear all permission caches (manifest, recent grants, active requests).
156    pub async fn clear_caches(&self) {
157        self.cache.clear().await;
158    }
159
160    /// Get a reference to the permission cache (for ensure* functions).
161    pub(crate) fn cache(&self) -> &cache::PermissionCache {
162        &self.cache
163    }
164
165    /// Get a reference to the callbacks (for ensure* functions).
166    pub(crate) fn callbacks(&self) -> &Option<Arc<dyn callbacks::PermissionCallbacks>> {
167        &self.callbacks
168    }
169
170    // -----------------------------------------------------------------------
171    // Public permission management methods
172    // -----------------------------------------------------------------------
173
174    /// Grant a permission by creating an on-chain token.
175    pub async fn grant_permission(
176        &self,
177        request: &PermissionRequest,
178        expiry: Option<u64>,
179    ) -> Result<(), bsv::wallet::error::WalletError> {
180        token_crud::grant_permission(self.inner.as_ref(), request, expiry, &self.admin_originator)
181            .await
182    }
183
184    /// Deny a permission request.
185    pub fn deny_permission(&self, request: &PermissionRequest) -> PermissionResponse {
186        token_crud::deny_permission(request)
187    }
188
189    /// Grant all permissions in a grouped set.
190    pub async fn grant_grouped_permission(
191        &self,
192        grouped: &GroupedPermissions,
193        expiry: Option<u64>,
194    ) -> Result<(), bsv::wallet::error::WalletError> {
195        token_crud::grant_grouped_permission(
196            self.inner.as_ref(),
197            grouped,
198            expiry,
199            &self.admin_originator,
200        )
201        .await
202    }
203
204    /// Deny all permissions in a grouped set.
205    pub fn deny_grouped_permission(&self, grouped: &GroupedPermissions) -> Vec<PermissionResponse> {
206        token_crud::deny_grouped_permission(grouped)
207    }
208
209    /// Revoke a single permission token.
210    pub async fn revoke_permission(
211        &self,
212        txid: &str,
213        output_index: u32,
214        permission_type: PermissionType,
215    ) -> Result<(), bsv::wallet::error::WalletError> {
216        let basket = match permission_type {
217            PermissionType::ProtocolPermission => types::BASKET_DPACP,
218            PermissionType::BasketAccess => types::BASKET_DBAP,
219            PermissionType::CertificateAccess => types::BASKET_DCAP,
220            PermissionType::SpendingAuthorization => types::BASKET_DSAP,
221        };
222        token_crud::revoke_permission_token(
223            self.inner.as_ref(),
224            txid,
225            output_index,
226            basket,
227            &self.admin_originator,
228        )
229        .await
230    }
231
232    /// Revoke all permission tokens for an originator.
233    pub async fn revoke_all_for_originator(
234        &self,
235        originator_domain: &str,
236    ) -> Result<u32, bsv::wallet::error::WalletError> {
237        token_crud::revoke_all_for_originator(
238            self.inner.as_ref(),
239            originator_domain,
240            &self.admin_originator,
241        )
242        .await
243    }
244
245    /// List all protocol permissions (DPACP) for an originator.
246    pub async fn list_protocol_permissions(
247        &self,
248        originator_domain: &str,
249    ) -> Result<Vec<PermissionToken>, bsv::wallet::error::WalletError> {
250        token_crud::list_protocol_permissions(
251            self.inner.as_ref(),
252            originator_domain,
253            &self.admin_originator,
254        )
255        .await
256    }
257
258    /// Check if an originator has a specific protocol permission.
259    pub async fn has_protocol_permission(
260        &self,
261        originator_domain: &str,
262        protocol: &str,
263        security_level: u8,
264        counterparty: &str,
265    ) -> Result<bool, bsv::wallet::error::WalletError> {
266        token_crud::has_protocol_permission(
267            self.inner.as_ref(),
268            originator_domain,
269            protocol,
270            security_level,
271            counterparty,
272            &self.admin_originator,
273        )
274        .await
275    }
276
277    /// List all basket access tokens (DBAP) for an originator.
278    pub async fn list_basket_access(
279        &self,
280        originator_domain: &str,
281    ) -> Result<Vec<PermissionToken>, bsv::wallet::error::WalletError> {
282        token_crud::list_basket_access(
283            self.inner.as_ref(),
284            originator_domain,
285            &self.admin_originator,
286        )
287        .await
288    }
289
290    /// Check if an originator has basket access permission.
291    pub async fn has_basket_access(
292        &self,
293        originator_domain: &str,
294        basket_name: &str,
295    ) -> Result<bool, bsv::wallet::error::WalletError> {
296        token_crud::has_basket_access(
297            self.inner.as_ref(),
298            originator_domain,
299            basket_name,
300            &self.admin_originator,
301        )
302        .await
303    }
304
305    /// List all certificate access tokens (DCAP) for an originator.
306    pub async fn list_certificate_access(
307        &self,
308        originator_domain: &str,
309    ) -> Result<Vec<PermissionToken>, bsv::wallet::error::WalletError> {
310        token_crud::list_certificate_access(
311            self.inner.as_ref(),
312            originator_domain,
313            &self.admin_originator,
314        )
315        .await
316    }
317
318    /// List all spending authorizations (DSAP) for an originator.
319    pub async fn list_spending_authorizations(
320        &self,
321        originator_domain: &str,
322    ) -> Result<Vec<PermissionToken>, bsv::wallet::error::WalletError> {
323        token_crud::list_spending_authorizations(
324            self.inner.as_ref(),
325            originator_domain,
326            &self.admin_originator,
327        )
328        .await
329    }
330}
331
332// ---------------------------------------------------------------------------
333// Helper: extract protocol info from args
334// ---------------------------------------------------------------------------
335
336/// Extract protocol name and security level from a Protocol struct.
337fn protocol_info(proto: &bsv::wallet::types::Protocol) -> (String, u8) {
338    (proto.protocol.clone(), proto.security_level)
339}
340
341/// Convert a Counterparty to a string for permission checks.
342fn counterparty_str(cpty: &bsv::wallet::types::Counterparty) -> String {
343    match cpty.counterparty_type {
344        bsv::wallet::types::CounterpartyType::Self_ => "self".to_string(),
345        bsv::wallet::types::CounterpartyType::Anyone => "anyone".to_string(),
346        bsv::wallet::types::CounterpartyType::Other => cpty
347            .public_key
348            .as_ref()
349            .map(|pk| pk.to_der_hex())
350            .unwrap_or_default(),
351        bsv::wallet::types::CounterpartyType::Uninitialized => String::new(),
352    }
353}
354
355/// Resolve originator, defaulting to empty string if None.
356fn orig(originator: Option<&str>) -> &str {
357    originator.unwrap_or("")
358}
359
360#[async_trait]
361impl WalletInterface for WalletPermissionsManager {
362    // -----------------------------------------------------------------------
363    // Auth/Info methods -- no permission checks needed, simple delegation
364    // -----------------------------------------------------------------------
365
366    async fn is_authenticated(
367        &self,
368        originator: Option<&str>,
369    ) -> Result<AuthenticatedResult, bsv::wallet::error::WalletError> {
370        self.inner.is_authenticated(originator).await
371    }
372
373    async fn wait_for_authentication(
374        &self,
375        originator: Option<&str>,
376    ) -> Result<AuthenticatedResult, bsv::wallet::error::WalletError> {
377        self.inner.wait_for_authentication(originator).await
378    }
379
380    async fn get_height(
381        &self,
382        originator: Option<&str>,
383    ) -> Result<GetHeightResult, bsv::wallet::error::WalletError> {
384        self.inner.get_height(originator).await
385    }
386
387    async fn get_header_for_height(
388        &self,
389        args: GetHeaderArgs,
390        originator: Option<&str>,
391    ) -> Result<GetHeaderResult, bsv::wallet::error::WalletError> {
392        self.inner.get_header_for_height(args, originator).await
393    }
394
395    async fn get_network(
396        &self,
397        originator: Option<&str>,
398    ) -> Result<GetNetworkResult, bsv::wallet::error::WalletError> {
399        self.inner.get_network(originator).await
400    }
401
402    async fn get_version(
403        &self,
404        originator: Option<&str>,
405    ) -> Result<GetVersionResult, bsv::wallet::error::WalletError> {
406        self.inner.get_version(originator).await
407    }
408
409    // -----------------------------------------------------------------------
410    // Action methods
411    // -----------------------------------------------------------------------
412
413    /// Checks spending authorization and basket access before delegating.
414    /// If `config.encrypt_wallet_metadata` is true, encrypts the description.
415    async fn create_action(
416        &self,
417        mut args: CreateActionArgs,
418        originator: Option<&str>,
419    ) -> Result<CreateActionResult, bsv::wallet::error::WalletError> {
420        let o = orig(originator);
421
422        // Check basket access for each output that has a basket
423        for output in &args.outputs {
424            if let Some(ref basket) = output.basket {
425                ensure::ensure_basket_access(self, o, basket, "insertion").await?;
426            }
427        }
428
429        // Check spending authorization if there are outputs with satoshis
430        let total_output_sats: u64 = args.outputs.iter().map(|o| o.satoshis).sum();
431        if total_output_sats > 0 {
432            ensure::ensure_spending_authorization(self, o, total_output_sats).await?;
433        }
434
435        // Encrypt metadata if configured
436        if self.config.encrypt_wallet_metadata {
437            args.description = metadata::encrypt_action_metadata(
438                self.inner.as_ref(),
439                &args.description,
440                &self.admin_originator,
441            )
442            .await?;
443        }
444
445        self.inner.create_action(args, originator).await
446    }
447
448    /// Permission checked on create_action, not here.
449    async fn sign_action(
450        &self,
451        args: SignActionArgs,
452        originator: Option<&str>,
453    ) -> Result<SignActionResult, bsv::wallet::error::WalletError> {
454        self.inner.sign_action(args, originator).await
455    }
456
457    /// No permission check needed for abort.
458    async fn abort_action(
459        &self,
460        args: AbortActionArgs,
461        originator: Option<&str>,
462    ) -> Result<AbortActionResult, bsv::wallet::error::WalletError> {
463        self.inner.abort_action(args, originator).await
464    }
465
466    /// Checks label access before delegating. Decrypts metadata if configured.
467    async fn list_actions(
468        &self,
469        args: ListActionsArgs,
470        originator: Option<&str>,
471    ) -> Result<ListActionsResult, bsv::wallet::error::WalletError> {
472        let o = orig(originator);
473
474        // Check label access for requested labels
475        if !args.labels.is_empty() {
476            ensure::ensure_label_access(self, o, &args.labels, "list").await?;
477        }
478
479        let mut result = self.inner.list_actions(args, originator).await?;
480
481        // Decrypt metadata if configured
482        if self.config.encrypt_wallet_metadata {
483            for action in &mut result.actions {
484                action.description = metadata::decrypt_action_metadata(
485                    self.inner.as_ref(),
486                    &action.description,
487                    &self.admin_originator,
488                )
489                .await?;
490            }
491        }
492
493        Ok(result)
494    }
495
496    /// Checks basket access for each output's target basket.
497    async fn internalize_action(
498        &self,
499        args: InternalizeActionArgs,
500        originator: Option<&str>,
501    ) -> Result<InternalizeActionResult, bsv::wallet::error::WalletError> {
502        // Basket access checks would apply to the target baskets in args.outputs.
503        // The InternalizeActionArgs structure is complex; for now we delegate
504        // as the specific basket extraction requires deeper args inspection
505        // that Plan 05 will complete with full integration tests.
506        self.inner.internalize_action(args, originator).await
507    }
508
509    // -----------------------------------------------------------------------
510    // Output methods
511    // -----------------------------------------------------------------------
512
513    /// Checks basket access before delegating. Decrypts metadata if configured.
514    async fn list_outputs(
515        &self,
516        args: ListOutputsArgs,
517        originator: Option<&str>,
518    ) -> Result<ListOutputsResult, bsv::wallet::error::WalletError> {
519        let o = orig(originator);
520        ensure::ensure_basket_access(self, o, &args.basket, "listing").await?;
521
522        let mut result = self.inner.list_outputs(args, originator).await?;
523
524        // Decrypt custom instructions metadata if configured
525        if self.config.encrypt_wallet_metadata {
526            for output in &mut result.outputs {
527                if let Some(ref instructions) = output.custom_instructions {
528                    output.custom_instructions = Some(
529                        metadata::decrypt_action_metadata(
530                            self.inner.as_ref(),
531                            instructions,
532                            &self.admin_originator,
533                        )
534                        .await?,
535                    );
536                }
537            }
538        }
539
540        Ok(result)
541    }
542
543    /// Checks basket access before delegating.
544    async fn relinquish_output(
545        &self,
546        args: RelinquishOutputArgs,
547        originator: Option<&str>,
548    ) -> Result<RelinquishOutputResult, bsv::wallet::error::WalletError> {
549        let o = orig(originator);
550        ensure::ensure_basket_access(self, o, &args.basket, "relinquishing").await?;
551        self.inner.relinquish_output(args, originator).await
552    }
553
554    // -----------------------------------------------------------------------
555    // Key/Crypto methods
556    // -----------------------------------------------------------------------
557
558    /// Checks protocol permission if config flag is set.
559    async fn get_public_key(
560        &self,
561        args: GetPublicKeyArgs,
562        originator: Option<&str>,
563    ) -> Result<GetPublicKeyResult, bsv::wallet::error::WalletError> {
564        let o = orig(originator);
565        if let Some(ref proto) = args.protocol_id {
566            let (name, sec) = protocol_info(proto);
567            let cpty = args
568                .counterparty
569                .as_ref()
570                .map(|c| counterparty_str(c))
571                .unwrap_or_default();
572            ensure::ensure_protocol_permission(
573                self,
574                o,
575                &name,
576                sec,
577                &cpty,
578                args.privileged,
579                "publicKey",
580            )
581            .await?;
582        }
583        self.inner.get_public_key(args, originator).await
584    }
585
586    /// Checks protocol permission for key linkage revelation.
587    /// RevealCounterpartyKeyLinkageArgs has counterparty as PublicKey, not Protocol.
588    /// We use a generic key linkage protocol check.
589    async fn reveal_counterparty_key_linkage(
590        &self,
591        args: RevealCounterpartyKeyLinkageArgs,
592        originator: Option<&str>,
593    ) -> Result<RevealCounterpartyKeyLinkageResult, bsv::wallet::error::WalletError> {
594        let o = orig(originator);
595        let cpty = args.counterparty.to_der_hex();
596        let privileged = args.privileged.unwrap_or(false);
597        ensure::ensure_protocol_permission(
598            self,
599            o,
600            "key linkage revelation",
601            2,
602            &cpty,
603            privileged,
604            "keyLinkage",
605        )
606        .await?;
607        self.inner
608            .reveal_counterparty_key_linkage(args, originator)
609            .await
610    }
611
612    /// Checks protocol permission for key linkage revelation.
613    async fn reveal_specific_key_linkage(
614        &self,
615        args: RevealSpecificKeyLinkageArgs,
616        originator: Option<&str>,
617    ) -> Result<RevealSpecificKeyLinkageResult, bsv::wallet::error::WalletError> {
618        let o = orig(originator);
619        let (name, sec) = protocol_info(&args.protocol_id);
620        let cpty = counterparty_str(&args.counterparty);
621        let privileged = args.privileged.unwrap_or(false);
622        ensure::ensure_protocol_permission(self, o, &name, sec, &cpty, privileged, "keyLinkage")
623            .await?;
624        self.inner
625            .reveal_specific_key_linkage(args, originator)
626            .await
627    }
628
629    /// Checks protocol permission for encryption.
630    async fn encrypt(
631        &self,
632        args: EncryptArgs,
633        originator: Option<&str>,
634    ) -> Result<EncryptResult, bsv::wallet::error::WalletError> {
635        let o = orig(originator);
636        let (name, sec) = protocol_info(&args.protocol_id);
637        let cpty = counterparty_str(&args.counterparty);
638        ensure::ensure_protocol_permission(
639            self,
640            o,
641            &name,
642            sec,
643            &cpty,
644            args.privileged,
645            "encrypting",
646        )
647        .await?;
648        self.inner.encrypt(args, originator).await
649    }
650
651    /// Checks protocol permission for decryption.
652    async fn decrypt(
653        &self,
654        args: DecryptArgs,
655        originator: Option<&str>,
656    ) -> Result<DecryptResult, bsv::wallet::error::WalletError> {
657        let o = orig(originator);
658        let (name, sec) = protocol_info(&args.protocol_id);
659        let cpty = counterparty_str(&args.counterparty);
660        ensure::ensure_protocol_permission(
661            self,
662            o,
663            &name,
664            sec,
665            &cpty,
666            args.privileged,
667            "decryption",
668        )
669        .await?;
670        self.inner.decrypt(args, originator).await
671    }
672
673    /// Checks protocol permission for HMAC creation.
674    async fn create_hmac(
675        &self,
676        args: CreateHmacArgs,
677        originator: Option<&str>,
678    ) -> Result<CreateHmacResult, bsv::wallet::error::WalletError> {
679        let o = orig(originator);
680        let (name, sec) = protocol_info(&args.protocol_id);
681        let cpty = counterparty_str(&args.counterparty);
682        ensure::ensure_protocol_permission(self, o, &name, sec, &cpty, args.privileged, "hmac")
683            .await?;
684        self.inner.create_hmac(args, originator).await
685    }
686
687    /// Checks protocol permission for HMAC verification.
688    async fn verify_hmac(
689        &self,
690        args: VerifyHmacArgs,
691        originator: Option<&str>,
692    ) -> Result<VerifyHmacResult, bsv::wallet::error::WalletError> {
693        let o = orig(originator);
694        let (name, sec) = protocol_info(&args.protocol_id);
695        let cpty = counterparty_str(&args.counterparty);
696        ensure::ensure_protocol_permission(
697            self,
698            o,
699            &name,
700            sec,
701            &cpty,
702            args.privileged,
703            "hmacVerification",
704        )
705        .await?;
706        self.inner.verify_hmac(args, originator).await
707    }
708
709    /// Checks protocol permission for signature creation.
710    async fn create_signature(
711        &self,
712        args: CreateSignatureArgs,
713        originator: Option<&str>,
714    ) -> Result<CreateSignatureResult, bsv::wallet::error::WalletError> {
715        let o = orig(originator);
716        let (name, sec) = protocol_info(&args.protocol_id);
717        let cpty = counterparty_str(&args.counterparty);
718        ensure::ensure_protocol_permission(self, o, &name, sec, &cpty, args.privileged, "signing")
719            .await?;
720        self.inner.create_signature(args, originator).await
721    }
722
723    /// Checks protocol permission for signature verification.
724    async fn verify_signature(
725        &self,
726        args: VerifySignatureArgs,
727        originator: Option<&str>,
728    ) -> Result<VerifySignatureResult, bsv::wallet::error::WalletError> {
729        let o = orig(originator);
730        let (name, sec) = protocol_info(&args.protocol_id);
731        let cpty = counterparty_str(&args.counterparty);
732        ensure::ensure_protocol_permission(
733            self,
734            o,
735            &name,
736            sec,
737            &cpty,
738            args.privileged,
739            "signatureVerification",
740        )
741        .await?;
742        self.inner.verify_signature(args, originator).await
743    }
744
745    // -----------------------------------------------------------------------
746    // Certificate methods
747    // -----------------------------------------------------------------------
748
749    /// Checks protocol permission for certificate acquisition before delegating.
750    ///
751    /// If `config.require_certificate_access_for_acquisition` is true,
752    /// calls `ensure_protocol_permission` with protocol
753    /// `"certificate acquisition {base64(cert_type)}"`.
754    async fn acquire_certificate(
755        &self,
756        args: AcquireCertificateArgs,
757        originator: Option<&str>,
758    ) -> Result<Certificate, bsv::wallet::error::WalletError> {
759        let o = orig(originator);
760        if self.config.require_certificate_access_for_acquisition {
761            let cert_type_str = BASE64_STANDARD.encode(args.cert_type.0);
762            ensure::ensure_protocol_permission(
763                self,
764                o,
765                &format!("certificate acquisition {}", cert_type_str),
766                1,
767                "self",
768                args.privileged,
769                "generic",
770            )
771            .await?;
772        }
773        self.inner.acquire_certificate(args, originator).await
774    }
775
776    /// Checks protocol permission for certificate listing before delegating.
777    ///
778    /// If `config.require_certificate_access_for_listing` is true,
779    /// calls `ensure_protocol_permission` with protocol `"certificate list"`.
780    async fn list_certificates(
781        &self,
782        args: ListCertificatesArgs,
783        originator: Option<&str>,
784    ) -> Result<ListCertificatesResult, bsv::wallet::error::WalletError> {
785        let o = orig(originator);
786        if self.config.require_certificate_access_for_listing {
787            ensure::ensure_protocol_permission(
788                self,
789                o,
790                "certificate list",
791                1,
792                "self",
793                *args.privileged,
794                "generic",
795            )
796            .await?;
797        }
798        self.inner.list_certificates(args, originator).await
799    }
800
801    /// Checks certificate access permission before proving a certificate.
802    ///
803    /// Always calls `ensure_certificate_access` with the certificate type,
804    /// fields to reveal, and verifier public key.
805    async fn prove_certificate(
806        &self,
807        args: ProveCertificateArgs,
808        originator: Option<&str>,
809    ) -> Result<ProveCertificateResult, bsv::wallet::error::WalletError> {
810        let o = orig(originator);
811        let cert_type_str = args
812            .certificate
813            .cert_type
814            .as_ref()
815            .map(|ct| BASE64_STANDARD.encode(ct.0))
816            .unwrap_or_default();
817        let verifier_hex = args.verifier.to_der_hex();
818        ensure::ensure_certificate_access(
819            self,
820            o,
821            &cert_type_str,
822            Some(&args.fields_to_reveal),
823            Some(&verifier_hex),
824            *args.privileged,
825            "disclosure",
826        )
827        .await?;
828        self.inner.prove_certificate(args, originator).await
829    }
830
831    /// Checks protocol permission for certificate relinquishment before delegating.
832    ///
833    /// If `config.require_certificate_access_for_relinquishing` is true,
834    /// calls `ensure_protocol_permission` with protocol
835    /// `"certificate relinquishment {base64(cert_type)}"`.
836    async fn relinquish_certificate(
837        &self,
838        args: RelinquishCertificateArgs,
839        originator: Option<&str>,
840    ) -> Result<RelinquishCertificateResult, bsv::wallet::error::WalletError> {
841        let o = orig(originator);
842        if self.config.require_certificate_access_for_relinquishing {
843            let cert_type_str = BASE64_STANDARD.encode(args.cert_type.0);
844            ensure::ensure_protocol_permission(
845                self,
846                o,
847                &format!("certificate relinquishment {}", cert_type_str),
848                1,
849                "self",
850                false,
851                "generic",
852            )
853            .await?;
854        }
855        self.inner.relinquish_certificate(args, originator).await
856    }
857
858    // -----------------------------------------------------------------------
859    // Discovery methods
860    // -----------------------------------------------------------------------
861
862    /// Checks certificate access if config flag is set.
863    /// Discovery args don't specify cert_type, so we use a generic "discovery" check.
864    async fn discover_by_identity_key(
865        &self,
866        args: DiscoverByIdentityKeyArgs,
867        originator: Option<&str>,
868    ) -> Result<DiscoverCertificatesResult, bsv::wallet::error::WalletError> {
869        if self.config.require_certificate_access_for_discovery {
870            let o = orig(originator);
871            ensure::ensure_certificate_access(self, o, "*", None, None, false, "discovery").await?;
872        }
873        self.inner.discover_by_identity_key(args, originator).await
874    }
875
876    /// Checks certificate access if config flag is set.
877    async fn discover_by_attributes(
878        &self,
879        args: DiscoverByAttributesArgs,
880        originator: Option<&str>,
881    ) -> Result<DiscoverCertificatesResult, bsv::wallet::error::WalletError> {
882        if self.config.require_certificate_access_for_discovery {
883            let o = orig(originator);
884            ensure::ensure_certificate_access(self, o, "*", None, None, false, "discovery").await?;
885        }
886        self.inner.discover_by_attributes(args, originator).await
887    }
888}
889
890#[cfg(test)]
891mod tests {
892    use super::*;
893    use crate::WalletError;
894
895    // -----------------------------------------------------------------------
896    // MockWalletInterface
897    // -----------------------------------------------------------------------
898
899    /// Simple mock wallet for testing the permissions manager.
900    /// All methods return `NotImplemented` except `is_authenticated` (returns true).
901    struct MockWalletInterface;
902
903    #[async_trait]
904    impl WalletInterface for MockWalletInterface {
905        async fn is_authenticated(
906            &self,
907            _originator: Option<&str>,
908        ) -> Result<AuthenticatedResult, bsv::wallet::error::WalletError> {
909            Ok(AuthenticatedResult {
910                authenticated: true,
911            })
912        }
913
914        async fn wait_for_authentication(
915            &self,
916            _originator: Option<&str>,
917        ) -> Result<AuthenticatedResult, bsv::wallet::error::WalletError> {
918            Err(bsv::wallet::error::WalletError::NotImplemented(
919                "wait_for_authentication".into(),
920            ))
921        }
922
923        async fn get_height(
924            &self,
925            _originator: Option<&str>,
926        ) -> Result<GetHeightResult, bsv::wallet::error::WalletError> {
927            Err(bsv::wallet::error::WalletError::NotImplemented(
928                "get_height".into(),
929            ))
930        }
931
932        async fn get_header_for_height(
933            &self,
934            _args: GetHeaderArgs,
935            _originator: Option<&str>,
936        ) -> Result<GetHeaderResult, bsv::wallet::error::WalletError> {
937            Err(bsv::wallet::error::WalletError::NotImplemented(
938                "get_header_for_height".into(),
939            ))
940        }
941
942        async fn get_network(
943            &self,
944            _originator: Option<&str>,
945        ) -> Result<GetNetworkResult, bsv::wallet::error::WalletError> {
946            Err(bsv::wallet::error::WalletError::NotImplemented(
947                "get_network".into(),
948            ))
949        }
950
951        async fn get_version(
952            &self,
953            _originator: Option<&str>,
954        ) -> Result<GetVersionResult, bsv::wallet::error::WalletError> {
955            Err(bsv::wallet::error::WalletError::NotImplemented(
956                "get_version".into(),
957            ))
958        }
959
960        async fn create_action(
961            &self,
962            _args: CreateActionArgs,
963            _originator: Option<&str>,
964        ) -> Result<CreateActionResult, bsv::wallet::error::WalletError> {
965            Err(bsv::wallet::error::WalletError::NotImplemented(
966                "create_action".into(),
967            ))
968        }
969
970        async fn sign_action(
971            &self,
972            _args: SignActionArgs,
973            _originator: Option<&str>,
974        ) -> Result<SignActionResult, bsv::wallet::error::WalletError> {
975            Err(bsv::wallet::error::WalletError::NotImplemented(
976                "sign_action".into(),
977            ))
978        }
979
980        async fn abort_action(
981            &self,
982            _args: AbortActionArgs,
983            _originator: Option<&str>,
984        ) -> Result<AbortActionResult, bsv::wallet::error::WalletError> {
985            Err(bsv::wallet::error::WalletError::NotImplemented(
986                "abort_action".into(),
987            ))
988        }
989
990        async fn list_actions(
991            &self,
992            _args: ListActionsArgs,
993            _originator: Option<&str>,
994        ) -> Result<ListActionsResult, bsv::wallet::error::WalletError> {
995            Err(bsv::wallet::error::WalletError::NotImplemented(
996                "list_actions".into(),
997            ))
998        }
999
1000        async fn internalize_action(
1001            &self,
1002            _args: InternalizeActionArgs,
1003            _originator: Option<&str>,
1004        ) -> Result<InternalizeActionResult, bsv::wallet::error::WalletError> {
1005            Err(bsv::wallet::error::WalletError::NotImplemented(
1006                "internalize_action".into(),
1007            ))
1008        }
1009
1010        async fn list_outputs(
1011            &self,
1012            _args: ListOutputsArgs,
1013            _originator: Option<&str>,
1014        ) -> Result<ListOutputsResult, bsv::wallet::error::WalletError> {
1015            Err(bsv::wallet::error::WalletError::NotImplemented(
1016                "list_outputs".into(),
1017            ))
1018        }
1019
1020        async fn relinquish_output(
1021            &self,
1022            _args: RelinquishOutputArgs,
1023            _originator: Option<&str>,
1024        ) -> Result<RelinquishOutputResult, bsv::wallet::error::WalletError> {
1025            Err(bsv::wallet::error::WalletError::NotImplemented(
1026                "relinquish_output".into(),
1027            ))
1028        }
1029
1030        async fn get_public_key(
1031            &self,
1032            _args: GetPublicKeyArgs,
1033            _originator: Option<&str>,
1034        ) -> Result<GetPublicKeyResult, bsv::wallet::error::WalletError> {
1035            Err(bsv::wallet::error::WalletError::NotImplemented(
1036                "get_public_key".into(),
1037            ))
1038        }
1039
1040        async fn reveal_counterparty_key_linkage(
1041            &self,
1042            _args: RevealCounterpartyKeyLinkageArgs,
1043            _originator: Option<&str>,
1044        ) -> Result<RevealCounterpartyKeyLinkageResult, bsv::wallet::error::WalletError> {
1045            Err(bsv::wallet::error::WalletError::NotImplemented(
1046                "reveal_counterparty_key_linkage".into(),
1047            ))
1048        }
1049
1050        async fn reveal_specific_key_linkage(
1051            &self,
1052            _args: RevealSpecificKeyLinkageArgs,
1053            _originator: Option<&str>,
1054        ) -> Result<RevealSpecificKeyLinkageResult, bsv::wallet::error::WalletError> {
1055            Err(bsv::wallet::error::WalletError::NotImplemented(
1056                "reveal_specific_key_linkage".into(),
1057            ))
1058        }
1059
1060        async fn encrypt(
1061            &self,
1062            _args: EncryptArgs,
1063            _originator: Option<&str>,
1064        ) -> Result<EncryptResult, bsv::wallet::error::WalletError> {
1065            Err(bsv::wallet::error::WalletError::NotImplemented(
1066                "encrypt".into(),
1067            ))
1068        }
1069
1070        async fn decrypt(
1071            &self,
1072            _args: DecryptArgs,
1073            _originator: Option<&str>,
1074        ) -> Result<DecryptResult, bsv::wallet::error::WalletError> {
1075            Err(bsv::wallet::error::WalletError::NotImplemented(
1076                "decrypt".into(),
1077            ))
1078        }
1079
1080        async fn create_hmac(
1081            &self,
1082            _args: CreateHmacArgs,
1083            _originator: Option<&str>,
1084        ) -> Result<CreateHmacResult, bsv::wallet::error::WalletError> {
1085            Err(bsv::wallet::error::WalletError::NotImplemented(
1086                "create_hmac".into(),
1087            ))
1088        }
1089
1090        async fn verify_hmac(
1091            &self,
1092            _args: VerifyHmacArgs,
1093            _originator: Option<&str>,
1094        ) -> Result<VerifyHmacResult, bsv::wallet::error::WalletError> {
1095            Err(bsv::wallet::error::WalletError::NotImplemented(
1096                "verify_hmac".into(),
1097            ))
1098        }
1099
1100        async fn create_signature(
1101            &self,
1102            _args: CreateSignatureArgs,
1103            _originator: Option<&str>,
1104        ) -> Result<CreateSignatureResult, bsv::wallet::error::WalletError> {
1105            Err(bsv::wallet::error::WalletError::NotImplemented(
1106                "create_signature".into(),
1107            ))
1108        }
1109
1110        async fn verify_signature(
1111            &self,
1112            _args: VerifySignatureArgs,
1113            _originator: Option<&str>,
1114        ) -> Result<VerifySignatureResult, bsv::wallet::error::WalletError> {
1115            Err(bsv::wallet::error::WalletError::NotImplemented(
1116                "verify_signature".into(),
1117            ))
1118        }
1119
1120        async fn acquire_certificate(
1121            &self,
1122            _args: AcquireCertificateArgs,
1123            _originator: Option<&str>,
1124        ) -> Result<Certificate, bsv::wallet::error::WalletError> {
1125            Err(bsv::wallet::error::WalletError::NotImplemented(
1126                "acquire_certificate".into(),
1127            ))
1128        }
1129
1130        async fn list_certificates(
1131            &self,
1132            _args: ListCertificatesArgs,
1133            _originator: Option<&str>,
1134        ) -> Result<ListCertificatesResult, bsv::wallet::error::WalletError> {
1135            Err(bsv::wallet::error::WalletError::NotImplemented(
1136                "list_certificates".into(),
1137            ))
1138        }
1139
1140        async fn prove_certificate(
1141            &self,
1142            _args: ProveCertificateArgs,
1143            _originator: Option<&str>,
1144        ) -> Result<ProveCertificateResult, bsv::wallet::error::WalletError> {
1145            Err(bsv::wallet::error::WalletError::NotImplemented(
1146                "prove_certificate".into(),
1147            ))
1148        }
1149
1150        async fn relinquish_certificate(
1151            &self,
1152            _args: RelinquishCertificateArgs,
1153            _originator: Option<&str>,
1154        ) -> Result<RelinquishCertificateResult, bsv::wallet::error::WalletError> {
1155            Err(bsv::wallet::error::WalletError::NotImplemented(
1156                "relinquish_certificate".into(),
1157            ))
1158        }
1159
1160        async fn discover_by_identity_key(
1161            &self,
1162            _args: DiscoverByIdentityKeyArgs,
1163            _originator: Option<&str>,
1164        ) -> Result<DiscoverCertificatesResult, bsv::wallet::error::WalletError> {
1165            Err(bsv::wallet::error::WalletError::NotImplemented(
1166                "discover_by_identity_key".into(),
1167            ))
1168        }
1169
1170        async fn discover_by_attributes(
1171            &self,
1172            _args: DiscoverByAttributesArgs,
1173            _originator: Option<&str>,
1174        ) -> Result<DiscoverCertificatesResult, bsv::wallet::error::WalletError> {
1175            Err(bsv::wallet::error::WalletError::NotImplemented(
1176                "discover_by_attributes".into(),
1177            ))
1178        }
1179    }
1180
1181    fn make_manager(admin: &str) -> WalletPermissionsManager {
1182        WalletPermissionsManager::new(
1183            Arc::new(MockWalletInterface),
1184            admin.to_string(),
1185            PermissionsManagerConfig::default(),
1186        )
1187    }
1188
1189    #[tokio::test]
1190    async fn test_admin_bypasses_all() {
1191        let mgr = make_manager("admin.example.com");
1192        // Admin originator should pass through to inner wallet
1193        let result = mgr.is_authenticated(Some("admin.example.com")).await;
1194        assert!(result.is_ok());
1195        let auth = result.unwrap();
1196        assert!(auth.authenticated);
1197    }
1198
1199    #[tokio::test]
1200    async fn test_non_admin_delegates() {
1201        let mgr = make_manager("admin.example.com");
1202        // Non-admin originator should also delegate (stubs pass through)
1203        let result = mgr.is_authenticated(Some("other.example.com")).await;
1204        assert!(result.is_ok());
1205        let auth = result.unwrap();
1206        assert!(auth.authenticated);
1207    }
1208
1209    #[tokio::test]
1210    async fn test_p_module_registration() {
1211        let mut mgr = make_manager("admin.example.com");
1212        assert!(mgr.permission_modules.is_empty());
1213
1214        // Create a simple test module
1215        struct TestModule;
1216
1217        #[async_trait]
1218        impl PermissionsModule for TestModule {
1219            async fn on_request(
1220                &self,
1221                _request: &types::PermissionRequest,
1222                _originator: &str,
1223            ) -> Result<types::PermissionResponse, WalletError> {
1224                Ok(types::PermissionResponse::EphemeralGrant)
1225            }
1226
1227            async fn on_response(
1228                &self,
1229                _request: &types::PermissionRequest,
1230                result: serde_json::Value,
1231                _originator: &str,
1232            ) -> Result<serde_json::Value, WalletError> {
1233                Ok(result)
1234            }
1235        }
1236
1237        mgr.register_module("test-scheme".to_string(), Arc::new(TestModule));
1238        assert_eq!(mgr.permission_modules.len(), 1);
1239        assert!(mgr.permission_modules.contains_key("test-scheme"));
1240        assert!(mgr.has_permission_module("test-scheme"));
1241        assert!(!mgr.has_permission_module("other-scheme"));
1242    }
1243
1244    #[tokio::test]
1245    async fn test_admin_normalization() {
1246        // Admin with port 443 should be normalized
1247        let mgr = make_manager("https://admin.example.com:443");
1248        assert!(mgr.is_admin(Some("admin.example.com")));
1249        assert!(mgr.is_admin(Some("ADMIN.EXAMPLE.COM")));
1250        assert!(!mgr.is_admin(Some("other.com")));
1251        assert!(!mgr.is_admin(None));
1252    }
1253
1254    #[tokio::test]
1255    async fn test_admin_originator_accessor() {
1256        let mgr = make_manager("admin.example.com");
1257        assert_eq!(mgr.admin_originator(), "admin.example.com");
1258    }
1259
1260    #[tokio::test]
1261    async fn test_deny_permission_via_manager() {
1262        let mgr = make_manager("admin.example.com");
1263        let req = types::PermissionRequest {
1264            permission_type: types::PermissionType::ProtocolPermission,
1265            originator: "example.com".to_string(),
1266            privileged: None,
1267            protocol: Some("test".to_string()),
1268            security_level: None,
1269            counterparty: None,
1270            basket_name: None,
1271            cert_type: None,
1272            cert_fields: None,
1273            verifier: None,
1274            amount: None,
1275            description: None,
1276            labels: None,
1277            is_new_user: false,
1278        };
1279        let resp = mgr.deny_permission(&req);
1280        match resp {
1281            types::PermissionResponse::Deny { reason } => {
1282                assert!(reason.contains("Protocol permission denied"));
1283            }
1284            _ => panic!("Expected Deny response"),
1285        }
1286    }
1287}