1use crate::auth::project_config::{
4 CreateOidcProviderConfigRequest, CreateSamlProviderConfigRequest,
5 ListOidcProviderConfigsResponse, ListSamlProviderConfigsResponse, OidcProviderConfig,
6 SamlProviderConfig, UpdateOidcProviderConfigRequest, UpdateSamlProviderConfigRequest,
7};
8use crate::auth::AuthError;
9use crate::core::middleware::AuthMiddleware;
10use reqwest::Client;
11use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
12use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
13use url::Url;
14
15const IDENTITY_TOOLKIT_URL: &str = "https://identitytoolkit.googleapis.com/v2";
16
17#[derive(Clone)]
19pub struct ProjectConfig {
20 client: ClientWithMiddleware,
21 base_url: String,
22}
23
24impl ProjectConfig {
25 pub(crate) fn new(middleware: AuthMiddleware) -> Self {
26 let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3);
27 let client = ClientBuilder::new(Client::new())
28 .with(RetryTransientMiddleware::new_with_policy(retry_policy))
29 .with(middleware.clone())
30 .build();
31
32 let project_id = middleware.key.project_id.clone().unwrap_or_default();
33 let base_url = format!("{}/projects/{}", IDENTITY_TOOLKIT_URL, project_id);
34
35 Self { client, base_url }
36 }
37
38 #[cfg(test)]
39 pub(crate) fn new_with_client(client: ClientWithMiddleware, base_url: String) -> Self {
40 Self { client, base_url }
41 }
42
43 pub async fn create_oidc_provider_config(
46 &self,
47 request: CreateOidcProviderConfigRequest,
48 ) -> Result<OidcProviderConfig, AuthError> {
49 let url = format!("{}/oauthIdpConfigs", self.base_url);
50 let mut url_obj = Url::parse(&url).map_err(|e| AuthError::ApiError(e.to_string()))?;
51 url_obj.query_pairs_mut().append_pair("oauthIdpConfigId", &request.oauth_idp_config_id);
52
53 let response = self
54 .client
55 .post(url_obj)
56 .json(&request)
57 .send()
58 .await?;
59
60 if !response.status().is_success() {
61 let status = response.status();
62 let text = response.text().await.unwrap_or_default();
63 return Err(AuthError::ApiError(format!(
64 "Create OIDC config failed {}: {}",
65 status, text
66 )));
67 }
68
69 let config: OidcProviderConfig = response.json().await?;
70 Ok(config)
71 }
72
73 pub async fn get_oidc_provider_config(
74 &self,
75 config_id: &str,
76 ) -> Result<OidcProviderConfig, AuthError> {
77 let url = format!("{}/oauthIdpConfigs/{}", self.base_url, config_id);
78
79 let response = self.client.get(&url).send().await?;
80
81 if !response.status().is_success() {
82 let status = response.status();
83 let text = response.text().await.unwrap_or_default();
84 return Err(AuthError::ApiError(format!(
85 "Get OIDC config failed {}: {}",
86 status, text
87 )));
88 }
89
90 let config: OidcProviderConfig = response.json().await?;
91 Ok(config)
92 }
93
94 pub async fn update_oidc_provider_config(
95 &self,
96 config_id: &str,
97 request: UpdateOidcProviderConfigRequest,
98 ) -> Result<OidcProviderConfig, AuthError> {
99 let url = format!("{}/oauthIdpConfigs/{}", self.base_url, config_id);
100
101 let mut mask_parts = Vec::new();
102 if request.display_name.is_some() { mask_parts.push("displayName"); }
103 if request.enabled.is_some() { mask_parts.push("enabled"); }
104 if request.client_id.is_some() { mask_parts.push("clientId"); }
105 if request.issuer.is_some() { mask_parts.push("issuer"); }
106 if request.client_secret.is_some() { mask_parts.push("clientSecret"); }
107 if request.response_type.is_some() { mask_parts.push("responseType"); }
108
109 let update_mask = mask_parts.join(",");
110
111 let mut url_obj = Url::parse(&url).map_err(|e| AuthError::ApiError(e.to_string()))?;
112 url_obj.query_pairs_mut().append_pair("updateMask", &update_mask);
113
114 let response = self
115 .client
116 .patch(url_obj)
117 .json(&request)
118 .send()
119 .await?;
120
121 if !response.status().is_success() {
122 let status = response.status();
123 let text = response.text().await.unwrap_or_default();
124 return Err(AuthError::ApiError(format!(
125 "Update OIDC config failed {}: {}",
126 status, text
127 )));
128 }
129
130 let config: OidcProviderConfig = response.json().await?;
131 Ok(config)
132 }
133
134 pub async fn delete_oidc_provider_config(&self, config_id: &str) -> Result<(), AuthError> {
135 let url = format!("{}/oauthIdpConfigs/{}", self.base_url, config_id);
136
137 let response = self.client.delete(&url).send().await?;
138
139 if !response.status().is_success() {
140 let status = response.status();
141 let text = response.text().await.unwrap_or_default();
142 return Err(AuthError::ApiError(format!(
143 "Delete OIDC config failed {}: {}",
144 status, text
145 )));
146 }
147
148 Ok(())
149 }
150
151 pub async fn list_oidc_provider_configs(
152 &self,
153 max_results: Option<u32>,
154 page_token: Option<&str>,
155 ) -> Result<ListOidcProviderConfigsResponse, AuthError> {
156 let url = format!("{}/oauthIdpConfigs", self.base_url);
157 let mut url_obj = Url::parse(&url).map_err(|e| AuthError::ApiError(e.to_string()))?;
158
159 {
160 let mut query_pairs = url_obj.query_pairs_mut();
161 if let Some(max) = max_results {
162 query_pairs.append_pair("pageSize", &max.to_string());
163 }
164 if let Some(token) = page_token {
165 query_pairs.append_pair("pageToken", token);
166 }
167 }
168
169 let response = self.client.get(url_obj).send().await?;
170
171 if !response.status().is_success() {
172 let status = response.status();
173 let text = response.text().await.unwrap_or_default();
174 return Err(AuthError::ApiError(format!(
175 "List OIDC configs failed {}: {}",
176 status, text
177 )));
178 }
179
180 let result: ListOidcProviderConfigsResponse = response.json().await?;
181 Ok(result)
182 }
183
184 pub async fn create_saml_provider_config(
187 &self,
188 request: CreateSamlProviderConfigRequest,
189 ) -> Result<SamlProviderConfig, AuthError> {
190 let url = format!("{}/inboundSamlConfigs", self.base_url);
191 let mut url_obj = Url::parse(&url).map_err(|e| AuthError::ApiError(e.to_string()))?;
192 url_obj.query_pairs_mut().append_pair("inboundSamlConfigId", &request.inbound_saml_config_id);
193
194 let response = self
195 .client
196 .post(url_obj)
197 .json(&request)
198 .send()
199 .await?;
200
201 if !response.status().is_success() {
202 let status = response.status();
203 let text = response.text().await.unwrap_or_default();
204 return Err(AuthError::ApiError(format!(
205 "Create SAML config failed {}: {}",
206 status, text
207 )));
208 }
209
210 let config: SamlProviderConfig = response.json().await?;
211 Ok(config)
212 }
213
214 pub async fn get_saml_provider_config(
215 &self,
216 config_id: &str,
217 ) -> Result<SamlProviderConfig, AuthError> {
218 let url = format!("{}/inboundSamlConfigs/{}", self.base_url, config_id);
219
220 let response = self.client.get(&url).send().await?;
221
222 if !response.status().is_success() {
223 let status = response.status();
224 let text = response.text().await.unwrap_or_default();
225 return Err(AuthError::ApiError(format!(
226 "Get SAML config failed {}: {}",
227 status, text
228 )));
229 }
230
231 let config: SamlProviderConfig = response.json().await?;
232 Ok(config)
233 }
234
235 pub async fn update_saml_provider_config(
236 &self,
237 config_id: &str,
238 request: UpdateSamlProviderConfigRequest,
239 ) -> Result<SamlProviderConfig, AuthError> {
240 let url = format!("{}/inboundSamlConfigs/{}", self.base_url, config_id);
241
242 let mut mask_parts = Vec::new();
243 if request.display_name.is_some() { mask_parts.push("displayName"); }
244 if request.enabled.is_some() { mask_parts.push("enabled"); }
245
246 if let Some(idp) = &request.idp_config {
248 if idp.idp_entity_id.is_some() { mask_parts.push("idpConfig.idpEntityId"); }
249 if idp.sso_url.is_some() { mask_parts.push("idpConfig.ssoUrl"); }
250 if idp.sign_request.is_some() { mask_parts.push("idpConfig.signRequest"); }
251 if idp.idp_certificates.is_some() { mask_parts.push("idpConfig.idpCertificates"); }
252 }
253
254 if let Some(sp) = &request.sp_config {
255 if sp.sp_entity_id.is_some() { mask_parts.push("spConfig.spEntityId"); }
256 if sp.callback_uri.is_some() { mask_parts.push("spConfig.callbackUri"); }
257 }
258
259 let update_mask = mask_parts.join(",");
260
261 let mut url_obj = Url::parse(&url).map_err(|e| AuthError::ApiError(e.to_string()))?;
262 url_obj.query_pairs_mut().append_pair("updateMask", &update_mask);
263
264 let response = self
265 .client
266 .patch(url_obj)
267 .json(&request)
268 .send()
269 .await?;
270
271 if !response.status().is_success() {
272 let status = response.status();
273 let text = response.text().await.unwrap_or_default();
274 return Err(AuthError::ApiError(format!(
275 "Update SAML config failed {}: {}",
276 status, text
277 )));
278 }
279
280 let config: SamlProviderConfig = response.json().await?;
281 Ok(config)
282 }
283
284 pub async fn delete_saml_provider_config(&self, config_id: &str) -> Result<(), AuthError> {
285 let url = format!("{}/inboundSamlConfigs/{}", self.base_url, config_id);
286
287 let response = self.client.delete(&url).send().await?;
288
289 if !response.status().is_success() {
290 let status = response.status();
291 let text = response.text().await.unwrap_or_default();
292 return Err(AuthError::ApiError(format!(
293 "Delete SAML config failed {}: {}",
294 status, text
295 )));
296 }
297
298 Ok(())
299 }
300
301 pub async fn list_saml_provider_configs(
302 &self,
303 max_results: Option<u32>,
304 page_token: Option<&str>,
305 ) -> Result<ListSamlProviderConfigsResponse, AuthError> {
306 let url = format!("{}/inboundSamlConfigs", self.base_url);
307 let mut url_obj = Url::parse(&url).map_err(|e| AuthError::ApiError(e.to_string()))?;
308
309 {
310 let mut query_pairs = url_obj.query_pairs_mut();
311 if let Some(max) = max_results {
312 query_pairs.append_pair("pageSize", &max.to_string());
313 }
314 if let Some(token) = page_token {
315 query_pairs.append_pair("pageToken", token);
316 }
317 }
318
319 let response = self.client.get(url_obj).send().await?;
320
321 if !response.status().is_success() {
322 let status = response.status();
323 let text = response.text().await.unwrap_or_default();
324 return Err(AuthError::ApiError(format!(
325 "List SAML configs failed {}: {}",
326 status, text
327 )));
328 }
329
330 let result: ListSamlProviderConfigsResponse = response.json().await?;
331 Ok(result)
332 }
333}