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