static_credstore_plugin/domain/
client.rs1use async_trait::async_trait;
2use credstore_sdk::{
3 CredStoreError, CredStorePluginClientV1, SecretMetadata, SecretRef, SecretValue,
4};
5use modkit_security::SecurityContext;
6
7use super::service::Service;
8
9#[async_trait]
10impl CredStorePluginClientV1 for Service {
11 async fn get(
12 &self,
13 ctx: &SecurityContext,
14 key: &SecretRef,
15 ) -> Result<Option<SecretMetadata>, CredStoreError> {
16 let tenant_id = ctx.subject_tenant_id();
17
18 let Some(entry) = self.get(tenant_id, key) else {
19 return Ok(None);
20 };
21
22 Ok(Some(SecretMetadata {
23 value: SecretValue::new(entry.value.as_bytes().to_vec()),
24 owner_id: entry.owner_id,
25 sharing: entry.sharing,
26 owner_tenant_id: entry.owner_tenant_id,
27 }))
28 }
29}
30
31#[cfg(test)]
32#[cfg_attr(coverage_nightly, coverage(off))]
33mod tests {
34 use super::*;
35 use crate::config::{SecretConfig, StaticCredStorePluginConfig};
36 use uuid::Uuid;
37
38 fn tenant_a() -> Uuid {
39 Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap()
40 }
41
42 fn tenant_b() -> Uuid {
43 Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap()
44 }
45
46 fn owner() -> Uuid {
47 Uuid::parse_str("33333333-3333-3333-3333-333333333333").unwrap()
48 }
49
50 fn ctx_for_tenant(tenant_id: Uuid) -> SecurityContext {
51 SecurityContext::builder()
52 .subject_id(owner())
53 .subject_tenant_id(tenant_id)
54 .build()
55 .unwrap()
56 }
57
58 fn service_with_single_secret() -> Service {
59 let cfg = StaticCredStorePluginConfig {
60 secrets: vec![SecretConfig {
61 tenant_id: tenant_a(),
62 owner_id: owner(),
63 key: "openai_api_key".to_owned(),
64 value: "sk-test-123".to_owned(),
65 sharing: credstore_sdk::SharingMode::Tenant,
66 }],
67 ..StaticCredStorePluginConfig::default()
68 };
69
70 Service::from_config(&cfg).unwrap()
71 }
72
73 #[tokio::test]
74 async fn get_returns_metadata_for_matching_tenant_and_key() {
75 let service = service_with_single_secret();
76 let plugin: &dyn CredStorePluginClientV1 = &service;
77 let key = SecretRef::new("openai_api_key").unwrap();
78
79 let result = plugin.get(&ctx_for_tenant(tenant_a()), &key).await;
80 assert!(result.is_ok());
81
82 let metadata = result.unwrap();
83 assert!(metadata.is_some());
84 let metadata = metadata.unwrap();
85 assert_eq!(metadata.value.as_bytes(), b"sk-test-123");
86 assert_eq!(metadata.owner_id, owner());
87 assert_eq!(metadata.owner_tenant_id, tenant_a());
88 }
89
90 #[tokio::test]
91 async fn get_returns_none_for_other_tenant() {
92 let service = service_with_single_secret();
93 let plugin: &dyn CredStorePluginClientV1 = &service;
94 let key = SecretRef::new("openai_api_key").unwrap();
95
96 let result = plugin.get(&ctx_for_tenant(tenant_b()), &key).await;
97 assert!(result.is_ok());
98 assert!(result.unwrap().is_none());
99 }
100
101 #[tokio::test]
102 async fn get_returns_none_for_missing_key() {
103 let service = service_with_single_secret();
104 let plugin: &dyn CredStorePluginClientV1 = &service;
105 let key = SecretRef::new("missing").unwrap();
106
107 let result = plugin.get(&ctx_for_tenant(tenant_a()), &key).await;
108 assert!(result.is_ok());
109 assert!(result.unwrap().is_none());
110 }
111
112 #[tokio::test]
113 async fn get_returns_none_when_no_secrets_configured() {
114 let service = Service::from_config(&StaticCredStorePluginConfig::default()).unwrap();
115 let plugin: &dyn CredStorePluginClientV1 = &service;
116 let key = SecretRef::new("openai_api_key").unwrap();
117
118 let result = plugin.get(&ctx_for_tenant(tenant_a()), &key).await;
119 assert!(result.is_ok());
120 assert!(result.unwrap().is_none());
121 }
122}