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