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