1use bitwarden_core::Client;
2
3use crate::{
4 error::SecretsManagerError,
5 secrets::{
6 SecretCreateRequest, SecretGetRequest, SecretIdentifiersByProjectRequest,
7 SecretIdentifiersRequest, SecretIdentifiersResponse, SecretPutRequest, SecretResponse,
8 SecretsDeleteRequest, SecretsDeleteResponse, SecretsGetRequest, SecretsResponse,
9 SecretsSyncRequest, SecretsSyncResponse, create_secret, delete_secrets, get_secret,
10 get_secrets_by_ids, list_secrets, list_secrets_by_project, sync_secrets, update_secret,
11 },
12};
13
14#[allow(missing_docs)]
15pub struct SecretsClient {
16 client: Client,
17}
18
19impl SecretsClient {
20 #[allow(missing_docs)]
21 pub fn new(client: Client) -> Self {
22 Self { client }
23 }
24
25 #[allow(missing_docs)]
26 pub async fn get(
27 &self,
28 input: &SecretGetRequest,
29 ) -> Result<SecretResponse, SecretsManagerError> {
30 get_secret(&self.client, input).await
31 }
32
33 #[allow(missing_docs)]
34 pub async fn get_by_ids(
35 &self,
36 input: SecretsGetRequest,
37 ) -> Result<SecretsResponse, SecretsManagerError> {
38 get_secrets_by_ids(&self.client, input).await
39 }
40
41 #[allow(missing_docs)]
42 pub async fn create(
43 &self,
44 input: &SecretCreateRequest,
45 ) -> Result<SecretResponse, SecretsManagerError> {
46 create_secret(&self.client, input).await
47 }
48
49 #[allow(missing_docs)]
50 pub async fn list(
51 &self,
52 input: &SecretIdentifiersRequest,
53 ) -> Result<SecretIdentifiersResponse, SecretsManagerError> {
54 list_secrets(&self.client, input).await
55 }
56
57 #[allow(missing_docs)]
58 pub async fn list_by_project(
59 &self,
60 input: &SecretIdentifiersByProjectRequest,
61 ) -> Result<SecretIdentifiersResponse, SecretsManagerError> {
62 list_secrets_by_project(&self.client, input).await
63 }
64
65 #[allow(missing_docs)]
66 pub async fn update(
67 &self,
68 input: &SecretPutRequest,
69 ) -> Result<SecretResponse, SecretsManagerError> {
70 update_secret(&self.client, input).await
71 }
72
73 #[allow(missing_docs)]
74 pub async fn delete(
75 &self,
76 input: SecretsDeleteRequest,
77 ) -> Result<SecretsDeleteResponse, SecretsManagerError> {
78 delete_secrets(&self.client, input).await
79 }
80
81 #[allow(missing_docs)]
82 pub async fn sync(
83 &self,
84 input: &SecretsSyncRequest,
85 ) -> Result<SecretsSyncResponse, SecretsManagerError> {
86 sync_secrets(&self.client, input).await
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use bitwarden_core::{ClientSettings, DeviceType, auth::login::AccessTokenLoginRequest};
93
94 use crate::{
95 SecretsManagerClient,
96 secrets::{SecretGetRequest, SecretIdentifiersRequest},
97 };
98
99 async fn start_mock(
100 mocks: Vec<wiremock::Mock>,
101 ) -> (wiremock::MockServer, SecretsManagerClient) {
102 let server = wiremock::MockServer::start().await;
103
104 for mock in mocks {
105 server.register(mock).await;
106 }
107
108 let settings = ClientSettings {
109 identity_url: format!("http://{}/identity", server.address()),
110 api_url: format!("http://{}/api", server.address()),
111 user_agent: "Bitwarden Rust-SDK [TEST]".into(),
112 device_type: DeviceType::SDK,
113 device_identifier: None,
114 bitwarden_client_version: None,
115 bitwarden_package_type: None,
116 };
117
118 (server, SecretsManagerClient::new(Some(settings)))
119 }
120
121 #[tokio::test]
122 async fn test_access_token_login() {
123 use wiremock::{Mock, ResponseTemplate, matchers};
124
125 let (_server, client) = start_mock(vec![
127 Mock::given(matchers::path("/identity/connect/token"))
128 .respond_with(ResponseTemplate::new(200).set_body_json(
129 serde_json::json!({
130 "access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjMwMURENkE1MEU4NEUxRDA5MUM4MUQzQjAwQkY5MDEwQzg1REJEOUFSUzI1NiIsInR5cCI6\
131 ImF0K2p3dCIsIng1dCI6Ik1CM1dwUTZFNGRDUnlCMDdBTC1RRU1oZHZabyJ9.eyJuYmYiOjE2NzUxMDM3ODEsImV4cCI6MTY3NTEwNzM4MSwiaXNzIjo\
132 iaHR0cDovL2xvY2FsaG9zdCIsImNsaWVudF9pZCI6ImVjMmMxZDQ2LTZhNGItNDc1MS1hMzEwLWFmOTYwMTMxN2YyZCIsInN1YiI6ImQzNDgwNGNhLTR\
133 mNmMtNDM5Mi04NmI3LWFmOTYwMTMxNzVkMCIsIm9yZ2FuaXphdGlvbiI6ImY0ZTQ0YTdmLTExOTAtNDMyYS05ZDRhLWFmOTYwMTMxMjdjYiIsImp0aSI\
134 6IjU3QUU0NzQ0MzIwNzk1RThGQkQ4MUIxNDA2RDQyNTQyIiwiaWF0IjoxNjc1MTAzNzgxLCJzY29wZSI6WyJhcGkuc2VjcmV0cyJdfQ.GRKYzqgJZHEE\
135 ZHsJkhVZH8zjYhY3hUvM4rhdV3FU10WlCteZdKHrPIadCUh-Oz9DxIAA2HfALLhj1chL4JgwPmZgPcVS2G8gk8XeBmZXowpVWJ11TXS1gYrM9syXbv9j\
136 0JUCdpeshH7e56WnlpVynyUwIum9hmYGZ_XJUfmGtlKLuNjYnawTwLEeR005uEjxq3qI1kti-WFnw8ciL4a6HLNulgiFw1dAvs4c7J0souShMfrnFO3g\
137 SOHff5kKD3hBB9ynDBnJQSFYJ7dFWHIjhqs0Vj-9h0yXXCcHvu7dVGpaiNjNPxbh6YeXnY6UWcmHLDtFYsG2BWcNvVD4-VgGxXt3cMhrn7l3fSYuo32Z\
138 Yk4Wop73XuxqF2fmfmBdZqGI1BafhENCcZw_bpPSfK2uHipfztrgYnrzwvzedz0rjFKbhDyrjzuRauX5dqVJ4ntPeT9g_I5n71gLxiP7eClyAx5RxdF6\
139 He87NwC8i-hLBhugIvLTiDj-Sk9HvMth6zaD0ebxd56wDjq8-CMG_WcgusDqNzKFHqWNDHBXt8MLeTgZAR2rQMIMFZqFgsJlRflbig8YewmNUA9wAU74\
140 TfxLY1foO7Xpg49vceB7C-PlvGi1VtX6F2i0tc_67lA5kWXnnKBPBUyspoIrmAUCwfms5nTTqA9xXAojMhRHAos_OdM",
141 "expires_in":3600,
142 "token_type":"Bearer",
143 "scope":"api.secrets",
144 "encrypted_payload":"2.E9fE8+M/VWMfhhim1KlCbQ==|eLsHR484S/tJbIkM6spnG/HP65tj9A6Tba7kAAvUp+rYuQmGLixiOCfMsqt5OvBctDfvvr/Aes\
145 Bu7cZimPLyOEhqEAjn52jF0eaI38XZfeOG2VJl0LOf60Wkfh3ryAMvfvLj3G4ZCNYU8sNgoC2+IQ==|lNApuCQ4Pyakfo/wwuuajWNaEX/2MW8/3rjXB/V7n+k="})
146 )),
147 Mock::given(matchers::path("/api/organizations/f4e44a7f-1190-432a-9d4a-af96013127cb/secrets"))
148 .respond_with(ResponseTemplate::new(200).set_body_json(
149 serde_json::json!({
150 "secrets":[{
151 "id":"15744a66-341a-4c62-af50-af960166b6bc",
152 "organizationId":"f4e44a7f-1190-432a-9d4a-af96013127cb",
153 "key":"2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=",
154 "creationDate":"2023-01-26T21:46:02.2182556Z",
155 "revisionDate":"2023-01-26T21:46:02.2182557Z"
156 }],
157 "projects":[],
158 "object":"SecretsWithProjectsList"
159 })
160 )),
161 Mock::given(matchers::path("/api/secrets/15744a66-341a-4c62-af50-af960166b6bc"))
162 .respond_with(ResponseTemplate::new(200).set_body_json(
163 serde_json::json!({
164 "id":"15744a66-341a-4c62-af50-af960166b6bc",
165 "organizationId":"f4e44a7f-1190-432a-9d4a-af96013127cb",
166 "key":"2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=",
167 "value":"2.Gl34n9JYABC7V21qHcBzHg==|c1Ds244pob7i+8+MXe4++w==|Shimz/qKMYZmzSFWdeBzFb9dFz7oF6Uv9oqkws7rEe0=",
168 "note":"2.Cn9ABJy7+WfR4uUHwdYepg==|+nbJyU/6hSknoa5dcEJEUg==|1DTp/ZbwGO3L3RN+VMsCHz8XDr8egn/M5iSitGGysPA=",
169 "creationDate":"2023-01-26T21:46:02.2182556Z",
170 "revisionDate":"2023-01-26T21:46:02.2182557Z",
171 "object":"secret"
172 })
173 ))
174 ]).await;
175
176 let res = client
178 .auth()
179 .login_access_token(&AccessTokenLoginRequest {
180 access_token: "0.ec2c1d46-6a4b-4751-a310-af9601317f2d.C2IgxjjLF7qSshsbwe8JGcbM075YXw:X8vbvA0bduihIDe/qrzIQQ==".into(),
181 state_file: None,
182 })
183 .await
184 .unwrap();
185 assert!(res.authenticated);
186
187 let organization_id = "f4e44a7f-1190-432a-9d4a-af96013127cb".try_into().unwrap();
188
189 let mut res = client
191 .secrets()
192 .list(&SecretIdentifiersRequest { organization_id })
193 .await
194 .unwrap();
195 assert_eq!(res.data.len(), 1);
196
197 let res = client
199 .secrets()
200 .get(&SecretGetRequest {
201 id: res.data.remove(0).id,
202 })
203 .await
204 .unwrap();
205 assert_eq!(res.key, "TEST");
206 assert_eq!(res.note, "TEST");
207 assert_eq!(res.value, "TEST");
208 }
209}