Skip to main content

qm_keycloak/
client.rs

1use std::{borrow::Cow, collections::HashMap, convert::identity, sync::Arc};
2
3use keycloak::types::{
4    ClientScopeRepresentation, ComponentRepresentation, IdentityProviderMapperRepresentation,
5    ProtocolMapperRepresentation,
6};
7pub use keycloak::{
8    types::{
9        self, AuthenticationExecutionInfoRepresentation, AuthenticationFlowRepresentation,
10        AuthenticatorConfigRepresentation, ClientRepresentation, CredentialRepresentation,
11        GroupRepresentation, IdentityProviderRepresentation, RealmRepresentation,
12        RoleRepresentation, TypeMap, UserRepresentation,
13    },
14    KeycloakAdmin, KeycloakError, KeycloakTokenSupplier,
15};
16use serde_json::Value;
17
18use crate::session::{KeycloakSession, KeycloakSessionClient};
19
20pub use crate::config::Config as KeycloakConfig;
21
22/// Server information for Keycloak.
23#[derive(Debug, serde::Deserialize, serde::Serialize)]
24pub struct ServerInfo {
25    /// Realm name.
26    #[serde(default)]
27    pub realm: Option<String>,
28}
29
30/// Realm information for Keycloak including public key.
31#[derive(Debug, serde::Deserialize, serde::Serialize)]
32pub struct RealmInfo {
33    /// Realm name.
34    #[serde(default)]
35    pub realm: Option<String>,
36    /// Public key for the realm.
37    #[serde(default)]
38    pub public_key: Option<String>,
39}
40
41async fn error_check(response: reqwest::Response) -> Result<reqwest::Response, KeycloakError> {
42    if !response.status().is_success() {
43        let status = response.status().into();
44        let text = response.text().await.unwrap_or_default();
45        return Err(KeycloakError::HttpFailure {
46            status,
47            body: serde_json::from_str(&text).ok(),
48            text,
49        });
50    }
51
52    Ok(response)
53}
54
55struct Inner {
56    url: Arc<str>,
57    config: KeycloakConfig,
58    client: reqwest::Client,
59    session: KeycloakSession,
60    admin: KeycloakAdmin<KeycloakSession>,
61}
62
63/// Builder for creating Keycloak client instances.
64///
65/// Use this to create a Keycloak client with custom configuration.
66#[derive(Default)]
67pub struct KeycloakBuilder {
68    no_refresh: bool,
69    env_prefix: Option<&'static str>,
70    config: Option<KeycloakConfig>,
71}
72
73impl KeycloakBuilder {
74    /// Disables token refresh.
75    pub fn with_no_refresh(mut self) -> Self {
76        self.no_refresh = true;
77        self
78    }
79
80    /// Sets environment variable prefix for configuration.
81    pub fn with_env_prefix(mut self, prefix: &'static str) -> Self {
82        self.env_prefix = Some(prefix);
83        self
84    }
85
86    /// Sets a custom environment variable for app url.
87    pub fn with_config(mut self, config: KeycloakConfig) -> Self {
88        self.config = Some(config);
89        self
90    }
91
92    /// Builds the Keycloak client.
93    pub async fn build(self) -> anyhow::Result<Keycloak> {
94        let mut config_builder = KeycloakConfig::builder();
95        let config = if let Some(config) = self.config {
96            config
97        } else {
98            if let Some(prefix) = self.env_prefix {
99                config_builder = config_builder.with_prefix(prefix);
100            }
101            config_builder.build()?
102        };
103        let refresh_token_enabled = !self.no_refresh;
104        let url: Arc<str> = Arc::from(config.address().to_string());
105        let username: Arc<str> = Arc::from(config.username().to_string());
106        let password: Arc<str> = Arc::from(config.password().to_string());
107        let client = reqwest::Client::new();
108        let session_client = KeycloakSessionClient::new(config.address(), "master", "admin-cli");
109        let session =
110            KeycloakSession::new(session_client, &username, &password, refresh_token_enabled)
111                .await?;
112        Ok(Keycloak {
113            inner: Arc::new(Inner {
114                url: url.clone(),
115                config,
116                client: client.clone(),
117                session: session.clone(),
118                admin: KeycloakAdmin::new(&url, session, client),
119            }),
120        })
121    }
122}
123
124/// Keycloak client for managing authentication and authorization.
125///
126/// Provides methods for realm, client, user, role, and token management.
127#[derive(Clone)]
128pub struct Keycloak {
129    inner: Arc<Inner>,
130}
131
132impl Keycloak {
133    /// Creates a new KeycloakBuilder.
134    pub fn builder() -> KeycloakBuilder {
135        KeycloakBuilder::default()
136    }
137
138    /// Returns the HTTP client.
139    pub fn http_client(&self) -> &reqwest::Client {
140        &self.inner.client
141    }
142
143    /// Creates a new Keycloak instance with default configuration.
144    pub async fn new() -> anyhow::Result<Self> {
145        KeycloakBuilder::default().build().await
146    }
147
148    /// App URLs, first one is used for root URL, and all are used to set redirect URIs.
149    pub fn app_urls(&self) -> Vec<&str> {
150        self.inner.config.app_urls()
151    }
152
153    /// Returns the public URL.
154    pub fn public_url(&self) -> &str {
155        self.inner.config.public_url()
156    }
157
158    /// Returns the Keycloak configuration.
159    pub fn config(&self) -> &KeycloakConfig {
160        &self.inner.config
161    }
162
163    /// Gets users in a realm.
164    pub async fn users(
165        &self,
166        realm: &str,
167        offset: Option<i32>,
168        page_size: Option<i32>,
169        search_query: Option<String>,
170    ) -> Result<Vec<UserRepresentation>, KeycloakError> {
171        self.inner
172            .admin
173            .realm(realm)
174            .users_get()
175            .first(offset)
176            .max(page_size)
177            .search(search_query)
178            .await
179            .map_err(|e| {
180                tracing::error!("{e:#?}");
181                e
182            })
183    }
184
185    /// Creates a new realm.
186    pub async fn create_realm(
187        &self,
188        realm_representation: RealmRepresentation,
189    ) -> Result<(), KeycloakError> {
190        self.inner
191            .admin
192            .post(realm_representation)
193            .await
194            .map_err(|e| {
195                tracing::error!("{e:#?}");
196                e
197            })?;
198
199        Ok(())
200    }
201
202    /// Removes a realm by name.
203    pub async fn remove_realm(&self, realm: &str) -> Result<(), KeycloakError> {
204        self.inner.admin.realm(realm).delete().await.map(|_| ())
205    }
206
207    /// Removes a group by ID.
208    pub async fn remove_group(&self, realm: &str, id: &str) -> Result<(), KeycloakError> {
209        self.inner
210            .admin
211            .realm(realm)
212            .groups_with_group_id_delete(id)
213            .await
214            .map(|_| ())
215            .map_err(|e| {
216                tracing::error!("{e:#?}");
217                e
218            })
219    }
220
221    /// Removes a group by path.
222    pub async fn remove_group_by_path(&self, realm: &str, path: &str) -> Result<(), KeycloakError> {
223        let group = self
224            .inner
225            .admin
226            .realm(realm)
227            .group_by_path_with_path_get(path)
228            .await
229            .map_err(|e| {
230                tracing::error!("{e:#?}");
231                e
232            })?;
233        self.remove_group(realm, group.id.as_deref().unwrap())
234            .await
235            .map_err(|e| {
236                tracing::error!("{e:#?}");
237                e
238            })
239    }
240
241    /// Removes a role by name.
242    pub async fn remove_role(&self, realm: &str, role_name: &str) -> Result<(), KeycloakError> {
243        self.inner
244            .admin
245            .realm(realm)
246            .roles_with_role_name_delete(role_name)
247            .await
248            .map(|_| ())
249            .map_err(|e| {
250                tracing::error!("{e:#?}");
251                e
252            })
253    }
254
255    /// Removes a role by ID.
256    pub async fn remove_role_by_id(&self, realm: &str, role_id: &str) -> Result<(), KeycloakError> {
257        self.inner
258            .admin
259            .realm(realm)
260            .roles_by_id_with_role_id_delete(role_id)
261            .await
262            .map(|_| ())
263            .map_err(|e| {
264                tracing::error!("{e:#?}");
265                e
266            })
267    }
268
269    /// Gets all realms.
270    pub async fn realms(&self) -> Result<Vec<String>, KeycloakError> {
271        let builder = self
272            .inner
273            .client
274            .get(format!("{}admin/realms", &self.inner.url));
275        let response = builder
276            .bearer_auth(self.inner.session.get(&self.inner.url).await?)
277            .send()
278            .await
279            .map_err(|e| {
280                tracing::error!("{e:#?}");
281                e
282            })?;
283        Ok(error_check(response)
284            .await?
285            .json::<Vec<ServerInfo>>()
286            .await?
287            .into_iter()
288            .filter_map(|r| {
289                if let Some(r) = r.realm {
290                    match r.as_str() {
291                        "master" => None,
292                        _ => Some(r),
293                    }
294                } else {
295                    None
296                }
297            })
298            .collect())
299    }
300
301    /// Gets all clients in a realm.
302    pub async fn clients(&self, realm: &str) -> Result<Vec<ClientRepresentation>, KeycloakError> {
303        let page_offset = 1000;
304        let mut offset = 0;
305        let mut clients = vec![];
306        loop {
307            let result = self
308                .inner
309                .admin
310                .realm(realm)
311                .clients_get()
312                .first(offset)
313                .max(page_offset)
314                .await
315                .map_err(|e| {
316                    tracing::error!("{e:#?}");
317                    e
318                })?;
319            if result.is_empty() {
320                break;
321            }
322            offset += page_offset;
323            clients.extend(result);
324        }
325        Ok(clients)
326    }
327
328    /// Gets a realm by name.
329    pub async fn realm_by_name(&self, realm: &str) -> Result<RealmRepresentation, KeycloakError> {
330        self.inner.admin.realm(realm).get().await.map_err(|e| {
331            tracing::error!("{e:#?}");
332            e
333        })
334    }
335
336    /// Updates a realm by name.
337    pub async fn update_realm_by_name(
338        &self,
339        realm: &str,
340        rep: RealmRepresentation,
341    ) -> Result<(), KeycloakError> {
342        self.inner
343            .admin
344            .realm(realm)
345            .put(rep)
346            .await
347            .map(|_| ())
348            .map_err(|e| {
349                tracing::error!("{e:#?}");
350                e
351            })
352    }
353
354    /// Gets all roles in a realm.
355    pub async fn roles(&self, realm: &str) -> Result<Vec<RoleRepresentation>, KeycloakError> {
356        self.inner
357            .admin
358            .realm(realm)
359            .roles_get()
360            .brief_representation(true)
361            .await
362            .map_err(|e| {
363                tracing::error!("{e:#?}");
364                e
365            })
366    }
367
368    /// Gets all roles in a realm (paginated).
369    pub async fn all_roles(&self, realm: &str) -> Result<Vec<RoleRepresentation>, KeycloakError> {
370        let page_offset = 1000;
371        let mut offset = 0;
372        let mut roles = vec![];
373        loop {
374            let result = self
375                .inner
376                .admin
377                .realm(realm)
378                .roles_get()
379                .brief_representation(true)
380                .first(offset)
381                .max(page_offset)
382                .await
383                .map_err(|e| {
384                    tracing::error!("{e:#?}");
385                    e
386                })?;
387            if result.is_empty() {
388                break;
389            }
390            offset += page_offset;
391            roles.extend(result);
392        }
393        Ok(roles)
394    }
395
396    /// Gets a realm role by name.
397    pub async fn realm_role_by_name(
398        &self,
399        realm: &str,
400        role_name: &str,
401    ) -> Result<RoleRepresentation, KeycloakError> {
402        self.inner
403            .admin
404            .realm(realm)
405            .roles_with_role_name_get(role_name)
406            .await
407    }
408
409    /// Creates a new role.
410    pub async fn create_role(
411        &self,
412        realm: &str,
413        rep: RoleRepresentation,
414    ) -> Result<Option<String>, KeycloakError> {
415        self.inner
416            .admin
417            .realm(realm)
418            .roles_post(rep)
419            .await
420            .map(|response| response.to_id().map(String::from))
421            .map_err(|e| {
422                tracing::error!("{e:#?}");
423                e
424            })
425    }
426
427    /// Creates a new group.
428    pub async fn create_group(
429        &self,
430        realm: &str,
431        rep: GroupRepresentation,
432    ) -> Result<Option<String>, KeycloakError> {
433        self.inner
434            .admin
435            .realm(realm)
436            .groups_post(rep)
437            .await
438            .map(|response| response.to_id().map(String::from))
439            .map_err(|e| {
440                tracing::error!("{e:#?}");
441                e
442            })
443    }
444
445    /// Gets a group by its path.
446    pub async fn group_by_path(
447        &self,
448        realm: &str,
449        path: &str,
450    ) -> Result<GroupRepresentation, KeycloakError> {
451        self.inner
452            .admin
453            .realm(realm)
454            .group_by_path_with_path_get(path)
455            .await
456    }
457
458    /// Gets a group and its children by ID.
459    pub async fn group_by_id_with_children(
460        &self,
461        realm: &str,
462        group_id: &str,
463        max: i32,
464    ) -> Result<Vec<GroupRepresentation>, KeycloakError> {
465        let subgroups = self
466            .inner
467            .admin
468            .realm(realm)
469            .groups_with_group_id_children_get(group_id)
470            .max(max)
471            .await?;
472
473        Ok(subgroups.into_iter().filter(|g| g.id.is_some()).collect())
474    }
475
476    /// Gets members of a role.
477    pub async fn role_members(
478        &self,
479        realm: &str,
480        role_name: &str,
481    ) -> Result<Vec<UserRepresentation>, KeycloakError> {
482        self.inner
483            .admin
484            .realm(realm)
485            .roles_with_role_name_users_get(role_name)
486            .await
487            .map_err(|e| {
488                tracing::error!("{e:#?}");
489                e
490            })
491    }
492
493    /// Creates a sub-group with a specific ID.
494    pub async fn create_sub_group_with_id(
495        &self,
496        realm: &str,
497        parent_id: &str,
498        rep: GroupRepresentation,
499    ) -> Result<(), KeycloakError> {
500        self.inner
501            .admin
502            .realm(realm)
503            .groups_with_group_id_children_post(parent_id, rep)
504            .await
505            .map_err(|e| {
506                tracing::error!("{e:#?}");
507                e
508            })?;
509        Ok(())
510    }
511
512    /// Creates realm role mappings for a group.
513    pub async fn create_realm_role_mappings_by_group_id(
514        &self,
515        realm: &str,
516        id: &str,
517        roles: Vec<RoleRepresentation>,
518    ) -> Result<Option<String>, KeycloakError> {
519        self.inner
520            .admin
521            .realm(realm)
522            .groups_with_group_id_role_mappings_realm_post(id, roles)
523            .await
524            .map(|response| response.to_id().map(String::from))
525            .map_err(|e| {
526                tracing::error!("{e:#?}");
527                e
528            })
529    }
530
531    /// Gets a user by ID.
532    pub async fn user_by_id(
533        &self,
534        realm: &str,
535        id: &str,
536    ) -> Result<Option<UserRepresentation>, KeycloakError> {
537        Ok(self
538            .inner
539            .admin
540            .realm(realm)
541            .users_with_user_id_get(id)
542            .user_profile_metadata(true)
543            .await
544            .map_err(|e| {
545                tracing::error!("{e:#?}");
546                e
547            })
548            .ok())
549    }
550
551    /// Gets a user by role.
552    pub async fn user_by_role(
553        &self,
554        realm: &str,
555        role_name: &str,
556    ) -> Result<Option<UserRepresentation>, KeycloakError> {
557        Ok(self
558            .inner
559            .admin
560            .realm(realm)
561            .roles_with_role_name_users_get(role_name)
562            .await
563            .map_err(|e| {
564                tracing::error!("{e:#?}");
565                e
566            })
567            .ok()
568            .and_then(|mut v| {
569                if !v.is_empty() {
570                    Some(v.remove(0))
571                } else {
572                    None
573                }
574            }))
575    }
576
577    /// Gets a user by username.
578    pub async fn user_by_username(
579        &self,
580        realm: &str,
581        username: String,
582    ) -> Result<Option<UserRepresentation>, KeycloakError> {
583        Ok(self
584            .inner
585            .admin
586            .realm(realm)
587            .users_get()
588            .brief_representation(false)
589            .exact(true)
590            .username(username)
591            .await
592            .map_err(|e| {
593                tracing::error!("{e:#?}");
594                e
595            })
596            .ok()
597            .and_then(|mut v| {
598                if !v.is_empty() {
599                    Some(v.remove(0))
600                } else {
601                    None
602                }
603            }))
604    }
605
606    /// Gets realm information.
607    pub async fn info(&self, realm: &str) -> Result<RealmInfo, KeycloakError> {
608        let builder = self
609            .inner
610            .client
611            .get(format!("{}/realms/{realm}", &self.inner.url));
612        let response = builder.send().await?;
613        Ok(error_check(response)
614            .await
615            .map_err(|e| {
616                tracing::error!("{e:#?}");
617                e
618            })?
619            .json()
620            .await?)
621    }
622
623    /// Gets a client by client_id.
624    pub async fn get_client(
625        &self,
626        realm: &str,
627    ) -> Result<Option<ClientRepresentation>, KeycloakError> {
628        Ok(self
629            .inner
630            .admin
631            .realm(realm)
632            .clients_get()
633            .client_id(self.config().client_id().to_owned())
634            .search(true)
635            .viewable_only(false)
636            .await
637            .map_err(|e| {
638                tracing::error!("{e:#?}");
639                e
640            })?
641            .pop())
642    }
643
644    /// Gets a client by ID.
645    pub async fn get_client_by_id(
646        &self,
647        realm: &str,
648        client_id: &str,
649    ) -> Result<Option<ClientRepresentation>, KeycloakError> {
650        Ok(self
651            .inner
652            .admin
653            .realm(realm)
654            .clients_get()
655            .client_id(client_id.to_owned())
656            .search(true)
657            .viewable_only(false)
658            .await
659            .map_err(|e| {
660                tracing::error!("{e:#?}");
661                e
662            })?
663            .pop())
664    }
665
666    /// Gets a client's service account user.
667    pub async fn get_client_service_account(
668        &self,
669        realm: &str,
670        client_uuid: &str,
671    ) -> Result<UserRepresentation, KeycloakError> {
672        self.inner
673            .admin
674            .realm(realm)
675            .clients_with_client_uuid_service_account_user_get(client_uuid)
676            .await
677            .map_err(|e| {
678                tracing::error!("{e:#?}");
679                e
680            })
681    }
682
683    /// Creates a new client.
684    pub async fn create_client(
685        &self,
686        realm: &str,
687        rep: ClientRepresentation,
688    ) -> Result<(), KeycloakError> {
689        self.inner
690            .admin
691            .realm(realm)
692            .clients_post(rep)
693            .await
694            .map_err(|e| {
695                tracing::error!("{e:#?}");
696                e
697            })?;
698        Ok(())
699    }
700
701    /// Removes a client by client_id.
702    pub async fn remove_client(&self, realm: &str, client_id: &str) -> Result<(), KeycloakError> {
703        let client = self
704            .get_client_by_id(realm, client_id)
705            .await?
706            .ok_or_else(|| KeycloakError::HttpFailure {
707                status: 404,
708                body: None,
709                text: format!("client with id: '{client_id}' not found"),
710            })?;
711        self.inner
712            .admin
713            .realm(realm)
714            .clients_with_client_uuid_delete(&client.id.unwrap())
715            .await
716            .map_err(|e| {
717                tracing::error!("{e:#?}");
718                e
719            })?;
720        Ok(())
721    }
722
723    /// Removes a client by UUID.
724    pub async fn remove_client_with_uuid(
725        &self,
726        realm: &str,
727        client_uuid: &str,
728    ) -> Result<(), KeycloakError> {
729        self.inner
730            .admin
731            .realm(realm)
732            .clients_with_client_uuid_delete(client_uuid)
733            .await
734            .map_err(|e| {
735                tracing::error!("{e:#?}");
736                e
737            })?;
738        Ok(())
739    }
740
741    /// Updates a client.
742    pub async fn update_client(
743        &self,
744        realm: &str,
745        id: &str,
746        rep: ClientRepresentation,
747    ) -> Result<(), KeycloakError> {
748        self.inner
749            .admin
750            .realm(realm)
751            .clients_with_client_uuid_put(id, rep)
752            .await
753            .map(|_| ())
754            .map_err(|e| {
755                tracing::error!("{e:#?}");
756                e
757            })
758    }
759
760    /// Gets client scopes.
761    pub async fn get_client_scopes(
762        &self,
763        realm: &str,
764    ) -> Result<Vec<ClientScopeRepresentation>, keycloak::KeycloakError> {
765        self.inner
766            .admin
767            .realm(realm)
768            .client_scopes_get()
769            .await
770            .map_err(|e| {
771                tracing::error!("{e:#?}");
772                e
773            })
774    }
775
776    /// Gets a client scope protocol mapper.
777    pub async fn get_client_scope_protocol_mapper(
778        &self,
779        realm: &str,
780        client_scope_id: &str,
781        id: &str,
782    ) -> Result<ProtocolMapperRepresentation, keycloak::KeycloakError> {
783        self.inner
784            .admin
785            .realm(realm)
786            .client_scopes_with_client_scope_id_protocol_mappers_models_with_id_get(
787                client_scope_id,
788                id,
789            )
790            .await
791            .map_err(|e| {
792                tracing::error!("{e:#?}");
793                e
794            })
795    }
796
797    /// Creates a client scope protocol mapper.
798    pub async fn create_client_scope_protocol_mapper(
799        &self,
800        realm: &str,
801        client_scope_id: &str,
802        rep: ProtocolMapperRepresentation,
803    ) -> Result<Option<String>, keycloak::KeycloakError> {
804        self.inner
805            .admin
806            .realm(realm)
807            .client_scopes_with_client_scope_id_protocol_mappers_models_post(client_scope_id, rep)
808            .await
809            .map(|response| response.to_id().map(String::from))
810            .map_err(|e| {
811                tracing::error!("{e:#?}");
812                e
813            })
814    }
815
816    /// Updates a client scope protocol mapper.
817    pub async fn update_client_scope_protocol_mapper(
818        &self,
819        realm: &str,
820        client_scope_id: &str,
821        id: &str,
822        rep: ProtocolMapperRepresentation,
823    ) -> Result<(), keycloak::KeycloakError> {
824        self.inner
825            .admin
826            .realm(realm)
827            .client_scopes_with_client_scope_id_protocol_mappers_models_with_id_put(
828                client_scope_id,
829                id,
830                rep,
831            )
832            .await
833            .map(|_| ())
834            .map_err(|e| {
835                tracing::error!("{e:#?}");
836                e
837            })
838    }
839
840    /// Removes a client scope protocol mapper.
841    pub async fn remove_client_scope_protocol_mapper(
842        &self,
843        realm: &str,
844        client_scope_id: &str,
845        id: &str,
846    ) -> Result<(), keycloak::KeycloakError> {
847        self.inner
848            .admin
849            .realm(realm)
850            .client_scopes_with_client_scope_id_protocol_mappers_models_with_id_delete(
851                client_scope_id,
852                id,
853            )
854            .await
855            .map(|_| ())
856            .map_err(|e| {
857                tracing::error!("{e:#?}");
858                e
859            })
860    }
861
862    /// Creates a new user.
863    pub async fn create_user(
864        &self,
865        realm: &str,
866        user: UserRepresentation,
867    ) -> Result<(), KeycloakError> {
868        self.inner
869            .admin
870            .realm(realm)
871            .users_post(user)
872            .await
873            .map_err(|e| {
874                tracing::error!("{e:#?}");
875                e
876            })?;
877        Ok(())
878    }
879
880    /// Updates a user's password.
881    pub async fn update_password(
882        &self,
883        realm: &str,
884        user_id: &str,
885        credential: CredentialRepresentation,
886    ) -> Result<(), KeycloakError> {
887        self.inner
888            .admin
889            .realm(realm)
890            .users_with_user_id_reset_password_put(user_id, credential)
891            .await
892            .map_err(|e| {
893                tracing::error!("{e:#?}");
894                e
895            })?;
896        Ok(())
897    }
898
899    /// Updates a user.
900    pub async fn update_user(
901        &self,
902        realm: &str,
903        user_id: &str,
904        user: &UserRepresentation,
905    ) -> Result<(), KeycloakError> {
906        self.inner
907            .admin
908            .realm(realm)
909            .users_with_user_id_put(user_id, user.to_owned())
910            .await
911            .map_err(|e| {
912                tracing::error!("{e:#?}");
913                e
914            })?;
915        Ok(())
916    }
917
918    /// Adds a user to a group.
919    pub async fn add_user_to_group(
920        &self,
921        realm: &str,
922        user_id: &str,
923        group_id: &str,
924    ) -> Result<(), KeycloakError> {
925        self.inner
926            .admin
927            .realm(realm)
928            .users_with_user_id_groups_with_group_id_put(user_id, group_id)
929            .await
930            .map_err(|e| {
931                tracing::error!("{e:#?}");
932                e
933            })?;
934        Ok(())
935    }
936
937    /// Adds a role to a user.
938    pub async fn add_user_role(
939        &self,
940        realm: &str,
941        user_id: &str,
942        role: RoleRepresentation,
943    ) -> Result<Option<String>, KeycloakError> {
944        self.inner
945            .admin
946            .realm(realm)
947            .users_with_user_id_role_mappings_realm_post(user_id, vec![role])
948            .await
949            .map(|response| response.to_id().map(String::from))
950            .map_err(|e| {
951                tracing::error!("{e:#?}");
952                e
953            })
954    }
955
956    /// Removes a user from a group.
957    pub async fn remove_user_from_group(
958        &self,
959        realm: &str,
960        user_id: &str,
961        group_id: &str,
962    ) -> Result<(), KeycloakError> {
963        self.inner
964            .admin
965            .realm(realm)
966            .users_with_user_id_groups_with_group_id_delete(user_id, group_id)
967            .await
968            .map_err(|e| {
969                tracing::error!("{e:#?}");
970                e
971            })?;
972        Ok(())
973    }
974
975    /// Removes roles from a user.
976    pub async fn remove_user_from_roles(
977        &self,
978        realm: &str,
979        user_id: &str,
980        roles: Vec<RoleRepresentation>,
981    ) -> Result<(), KeycloakError> {
982        self.inner
983            .admin
984            .realm(realm)
985            .users_with_user_id_role_mappings_realm_delete(user_id, roles)
986            .await
987            .map_err(|e| {
988                tracing::error!("{e:#?}");
989                e
990            })?;
991        Ok(())
992    }
993
994    /// Removes a user.
995    pub async fn remove_user(&self, realm: &str, user_id: &str) -> Result<(), KeycloakError> {
996        self.inner
997            .admin
998            .realm(realm)
999            .users_with_user_id_delete(user_id)
1000            .await
1001            .map_err(|e| {
1002                tracing::error!("{e:#?}");
1003                e
1004            })?;
1005        Ok(())
1006    }
1007
1008    /// Removes brute force status for a user.
1009    pub async fn remove_brute_force_for_user(
1010        &self,
1011        realm: &str,
1012        user_id: &str,
1013    ) -> Result<(), KeycloakError> {
1014        self.inner
1015            .admin
1016            .realm(realm)
1017            .attack_detection_brute_force_users_with_user_id_delete(user_id)
1018            .await
1019            .map_err(|e| {
1020                tracing::error!("{e:#?}");
1021                e
1022            })?;
1023        Ok(())
1024    }
1025
1026    /// Gets brute force status for a user.
1027    pub async fn get_brute_force_status_for_user(
1028        &self,
1029        realm: &str,
1030        user_id: &str,
1031    ) -> Result<bool, KeycloakError> {
1032        Ok(self
1033            .inner
1034            .admin
1035            .realm(realm)
1036            .attack_detection_brute_force_users_with_user_id_get(user_id)
1037            .await?
1038            .get("disabled")
1039            .and_then(Value::as_bool)
1040            .unwrap_or_default())
1041    }
1042
1043    /// Sends a verification email to a user.
1044    pub async fn send_verify_email_user(
1045        &self,
1046        realm: &str,
1047        user_id: &str,
1048        client_id: Option<String>,
1049        redirect_url: Option<String>,
1050    ) -> Result<(), KeycloakError> {
1051        self.inner
1052            .admin
1053            .realm(realm)
1054            .users_with_user_id_send_verify_email_put(user_id)
1055            .client_id(client_id)
1056            .redirect_uri(redirect_url)
1057            .await
1058            .map_err(|e| {
1059                tracing::error!("{e:#?}");
1060                e
1061            })?;
1062        Ok(())
1063    }
1064
1065    /// Sends a custom email to a user.
1066    pub async fn send_custom_email_user(
1067        &self,
1068        realm: &str,
1069        user_id: &str,
1070        client_id: Option<String>,
1071        redirect_url: Option<String>,
1072        body: Vec<String>,
1073    ) -> Result<(), KeycloakError> {
1074        self.inner
1075            .admin
1076            .realm(realm)
1077            .users_with_user_id_execute_actions_email_put(user_id, body)
1078            .client_id(client_id)
1079            .redirect_uri(redirect_url)
1080            .await
1081            .map_err(|e| {
1082                tracing::error!("{e:#?}");
1083                e
1084            })?;
1085        Ok(())
1086    }
1087
1088    /// Gets an error message from a KeycloakError.
1089    pub fn error_message<'e>(&self, err: &'e KeycloakError) -> Cow<'e, str> {
1090        match err {
1091            KeycloakError::ReqwestFailure(err) => Cow::Owned(err.to_string()),
1092            KeycloakError::HttpFailure { status, body, text } => body
1093                .as_ref()
1094                .and_then(|e| {
1095                    e.error
1096                        .as_deref()
1097                        .or(e.error_message.as_deref())
1098                        .map(Cow::Borrowed)
1099                })
1100                .unwrap_or_else(|| {
1101                    if !text.is_empty() {
1102                        Cow::Borrowed(text.as_str())
1103                    } else {
1104                        Cow::Owned(status.to_string())
1105                    }
1106                }),
1107        }
1108    }
1109
1110    /// Gets authentication flows.
1111    pub async fn get_authentication_flows(
1112        &self,
1113        realm: &str,
1114    ) -> Result<Vec<AuthenticationFlowRepresentation>, KeycloakError> {
1115        self.inner
1116            .admin
1117            .realm(realm)
1118            .authentication_flows_get()
1119            .await
1120    }
1121
1122    /// Copies an authentication flow.
1123    pub async fn copy_authentication_flow(
1124        &self,
1125        realm: &str,
1126        flowalias: &str,
1127        body: TypeMap<String, String>,
1128    ) -> Result<(), KeycloakError> {
1129        let response = self
1130            .inner
1131            .admin
1132            .realm(realm)
1133            .authentication_flows_with_flow_alias_copy_post(flowalias, body)
1134            .await;
1135        match response {
1136            Ok(_) => {
1137                tracing::info!("Copied successfully.");
1138                Ok(())
1139            }
1140            Err(e) => {
1141                tracing::error!("Failed to copy authentication flow: {e}");
1142                Err(e)
1143            }
1144        }
1145    }
1146
1147    /// Gets flow executions.
1148    pub async fn get_flow_executions(
1149        &self,
1150        realm: &str,
1151        flowalias: &str,
1152    ) -> Result<Vec<AuthenticationExecutionInfoRepresentation>, KeycloakError> {
1153        let result = self
1154            .inner
1155            .admin
1156            .realm(realm)
1157            .authentication_flows_with_flow_alias_executions_get(flowalias)
1158            .await;
1159        match result {
1160            Ok(response) => {
1161                tracing::info!("Getted flow executions successfully.");
1162                Ok(response)
1163            }
1164            Err(e) => {
1165                tracing::error!("Failed to get flow executions: {e}");
1166                Err(e)
1167            }
1168        }
1169    }
1170
1171    /// Removes an execution.
1172    pub async fn remove_execution(&self, realm: &str, id: &str) -> Result<(), KeycloakError> {
1173        let result = self
1174            .inner
1175            .admin
1176            .realm(realm)
1177            .authentication_executions_with_execution_id_delete(id)
1178            .await;
1179        match result {
1180            Ok(_) => {
1181                tracing::info!("Execution deleted successfully.");
1182                Ok(())
1183            }
1184            Err(e) => {
1185                tracing::error!("Failed to delete execution: {e}");
1186                Err(e)
1187            }
1188        }
1189    }
1190
1191    /// Creates a subflow.
1192    pub async fn create_subflow(
1193        &self,
1194        realm: &str,
1195        flowalias: &str,
1196        body: TypeMap<String, Value>,
1197    ) -> Result<(), KeycloakError> {
1198        let response = self
1199            .inner
1200            .admin
1201            .realm(realm)
1202            .authentication_flows_with_flow_alias_executions_flow_post(flowalias, body)
1203            .await;
1204        match response {
1205            Ok(_) => {
1206                tracing::info!("Subflow created successfully.");
1207                Ok(())
1208            }
1209            Err(e) => {
1210                tracing::error!("Failed to crete subflow: {e}");
1211                Err(e)
1212            }
1213        }
1214    }
1215
1216    /// Modifies a flow execution.
1217    pub async fn modify_flow_execution(
1218        &self,
1219        realm: &str,
1220        flowalias: &str,
1221        body: AuthenticationExecutionInfoRepresentation,
1222    ) -> Result<(), KeycloakError> {
1223        let response = self
1224            .inner
1225            .admin
1226            .realm(realm)
1227            .authentication_flows_with_flow_alias_executions_put(flowalias, body)
1228            .await;
1229        match response {
1230            Ok(_) => {
1231                tracing::info!("PUT flow execution successfully.");
1232                Ok(())
1233            }
1234            Err(e) => {
1235                tracing::error!("Failed PUT flow execution: {e}");
1236                Err(e)
1237            }
1238        }
1239    }
1240
1241    /// Creates a flow execution.
1242    pub async fn create_flow_execution(
1243        &self,
1244        realm: &str,
1245        flowalias: &str,
1246        body: TypeMap<String, Value>,
1247    ) -> Result<(), KeycloakError> {
1248        let response = self
1249            .inner
1250            .admin
1251            .realm(realm)
1252            .authentication_flows_with_flow_alias_executions_execution_post(flowalias, body)
1253            .await;
1254        match response {
1255            Ok(_) => {
1256                tracing::info!("Execution created successfully.");
1257                Ok(())
1258            }
1259            Err(e) => {
1260                tracing::error!("Failed to crete execution: {e}");
1261                Err(e)
1262            }
1263        }
1264    }
1265
1266    /// Adds an authenticator config.
1267    pub async fn add_authenticator_config(
1268        &self,
1269        realm: &str,
1270        execution_id: &str,
1271        body: AuthenticatorConfigRepresentation,
1272    ) -> Result<(), KeycloakError> {
1273        self.inner
1274            .admin
1275            .realm(realm)
1276            .authentication_executions_with_execution_id_config_post(execution_id, body)
1277            .await?;
1278        Ok(())
1279    }
1280
1281    /// Finds an identity provider.
1282    pub async fn find_identity_provider(
1283        &self,
1284        realm: &str,
1285        alias: &str,
1286    ) -> Result<IdentityProviderRepresentation, KeycloakError> {
1287        self.inner
1288            .admin
1289            .realm(realm)
1290            .identity_provider_instances_with_alias_get(alias)
1291            .await
1292    }
1293
1294    /// Adds a SAML identity provider.
1295    pub async fn add_saml_identity_provider(
1296        &self,
1297        realm: &str,
1298        alias: &str,
1299        metainfo_url: &str,
1300        entity_id: &str,
1301    ) -> Result<(), KeycloakError> {
1302        self.add_saml_identity_provider_custom(realm, alias, metainfo_url, entity_id, identity)
1303            .await
1304    }
1305
1306    /// Adds a custom SAML identity provider.
1307    pub async fn add_saml_identity_provider_custom<T>(
1308        &self,
1309        realm: &str,
1310        alias: &str,
1311        metainfo_url: &str,
1312        entity_id: &str,
1313        idp_representation_transform: T,
1314    ) -> Result<(), KeycloakError>
1315    where
1316        T: Fn(IdentityProviderRepresentation) -> IdentityProviderRepresentation,
1317    {
1318        let idp_config = [
1319            ("alias", alias),
1320            ("providerId", "saml"),
1321            ("fromUrl", metainfo_url),
1322        ]
1323        .into_iter()
1324        .map(|(key, value)| {
1325            (
1326                key.to_string(),
1327                serde_json::Value::String(value.to_string()),
1328            )
1329        })
1330        .collect();
1331
1332        let imported_config = self
1333            .inner
1334            .admin
1335            .realm(realm)
1336            .identity_provider_import_config_post(idp_config)
1337            .await?;
1338        self.add_saml_identity_provider_from_config(
1339            realm,
1340            alias,
1341            imported_config,
1342            entity_id,
1343            idp_representation_transform,
1344        )
1345        .await
1346    }
1347
1348    /// Adds a SAML identity provider from config.
1349    pub async fn add_saml_identity_provider_from_config<T>(
1350        &self,
1351        realm: &str,
1352        alias: &str,
1353        mut idp_config: HashMap<String, String>,
1354        entity_id: &str,
1355        idp_representation_transform: T,
1356    ) -> Result<(), KeycloakError>
1357    where
1358        T: Fn(IdentityProviderRepresentation) -> IdentityProviderRepresentation,
1359    {
1360        if idp_config.get("nameIDPolicyFormat").map(|s| s.as_str())
1361            == Some("urn:oasis:names:tc:SAML:2.0:nameid-format:transient")
1362        {
1363            idp_config.insert("principalType".into(), "ATTRIBUTE".into());
1364        }
1365
1366        idp_config.insert("entityId".into(), entity_id.into());
1367
1368        let idp_representation = IdentityProviderRepresentation {
1369            alias: Some(alias.to_string()),
1370            config: Some(idp_config),
1371            display_name: Some(alias.to_string()),
1372            enabled: Some(true),
1373            provider_id: Some("saml".to_string()),
1374            ..Default::default()
1375        };
1376
1377        let realm = self.inner.admin.realm(realm);
1378        realm
1379            .identity_provider_instances_post(idp_representation.clone())
1380            .await?;
1381
1382        realm
1383            .identity_provider_instances_with_alias_put(
1384                alias,
1385                idp_representation_transform(idp_representation),
1386            )
1387            .await?;
1388
1389        Ok(())
1390    }
1391
1392    /// Finds identity provider mappers.
1393    pub async fn find_identity_provider_mappers(
1394        &self,
1395        realm: &str,
1396        alias: &str,
1397    ) -> Result<Vec<IdentityProviderMapperRepresentation>, KeycloakError> {
1398        self.inner
1399            .admin
1400            .realm(realm)
1401            .identity_provider_instances_with_alias_mappers_get(alias)
1402            .await
1403    }
1404
1405    /// Adds an identity provider mapper.
1406    pub async fn add_identity_provider_mapper(
1407        &self,
1408        realm: &str,
1409        alias: &str,
1410        mapper: IdentityProviderMapperRepresentation,
1411    ) -> Result<(), KeycloakError> {
1412        self.inner
1413            .admin
1414            .realm(realm)
1415            .identity_provider_instances_with_alias_mappers_post(alias, mapper)
1416            .await?;
1417        Ok(())
1418    }
1419
1420    /// Updates an identity provider mapper.
1421    pub async fn update_identity_provider_mapper(
1422        &self,
1423        realm: &str,
1424        alias: &str,
1425        mapper_id: &str,
1426        mapper: IdentityProviderMapperRepresentation,
1427    ) -> Result<(), KeycloakError> {
1428        self.inner
1429            .admin
1430            .realm(realm)
1431            .identity_provider_instances_with_alias_mappers_with_id_put(alias, mapper_id, mapper)
1432            .await?;
1433        Ok(())
1434    }
1435
1436    /// Finds key providers.
1437    pub async fn find_key_providers(
1438        &self,
1439        realm: &str,
1440    ) -> Result<Vec<ComponentRepresentation>, KeycloakError> {
1441        self.inner
1442            .admin
1443            .realm(realm)
1444            .components_get()
1445            .type_("org.keycloak.keys.KeyProvider".to_string())
1446            .await
1447    }
1448
1449    /// Adds a key provider.
1450    pub async fn add_key_provider(
1451        &self,
1452        realm: &str,
1453        mut key_provider: ComponentRepresentation,
1454    ) -> Result<(), KeycloakError> {
1455        key_provider.provider_type = Some("org.keycloak.keys.KeyProvider".into());
1456        self.inner
1457            .admin
1458            .realm(realm)
1459            .components_post(key_provider)
1460            .await?;
1461        Ok(())
1462    }
1463
1464    /// Deletes a component.
1465    pub async fn delete_component(&self, realm: &str, id: &str) -> Result<(), KeycloakError> {
1466        self.inner
1467            .admin
1468            .realm(realm)
1469            .components_with_id_delete(id)
1470            .await
1471            .map(|_| ())
1472    }
1473
1474    /// Modifies a component.
1475    pub async fn modify_component(
1476        &self,
1477        realm: &str,
1478        id: &str,
1479        component: ComponentRepresentation,
1480    ) -> Result<(), KeycloakError> {
1481        self.inner
1482            .admin
1483            .realm(realm)
1484            .components_with_id_put(id, component)
1485            .await
1486            .map(|_| ())
1487    }
1488
1489    /// Gets user groups.
1490    pub async fn user_groups(
1491        &self,
1492        realm: &str,
1493        user_id: &str,
1494    ) -> Result<Vec<keycloak::types::GroupRepresentation>, keycloak::KeycloakError> {
1495        self.inner
1496            .admin
1497            .realm(realm)
1498            .users_with_user_id_groups_get(user_id)
1499            .brief_representation(true)
1500            .await
1501    }
1502
1503    /// Gets user roles.
1504    pub async fn user_roles(
1505        &self,
1506        realm: &str,
1507        user_id: &str,
1508    ) -> Result<Vec<keycloak::types::RoleRepresentation>, keycloak::KeycloakError> {
1509        self.inner
1510            .admin
1511            .realm(realm)
1512            .users_with_user_id_role_mappings_realm_get(user_id)
1513            .await
1514    }
1515
1516    /// Imports identity provider config.
1517    pub async fn identity_provider_import_config(
1518        &self,
1519        realm: &str,
1520        provider_id: String,
1521        file: Vec<u8>,
1522    ) -> Result<HashMap<String, String>, keycloak::KeycloakError> {
1523        self.inner
1524            .admin
1525            .realm_identity_provider_import_config_post_form(realm, provider_id, file)
1526            .await
1527    }
1528
1529    /// Imports SAML identity provider config.
1530    pub async fn identity_provider_import_saml_config(
1531        &self,
1532        realm: &str,
1533        file: Vec<u8>,
1534    ) -> Result<HashMap<String, String>, keycloak::KeycloakError> {
1535        self.identity_provider_import_config(realm, "saml".to_string(), file)
1536            .await
1537    }
1538}
1539
1540/// Sets up IDP signature and encryption settings.
1541pub fn idp_signature_and_encryption(
1542    mut idp: IdentityProviderRepresentation,
1543    principal_attribute: &str,
1544) -> IdentityProviderRepresentation {
1545    if let Some(config) = &mut idp.config {
1546        config.extend(
1547            [
1548                ("allowCreate", "true"),
1549                ("allowedClockSkew", "0"),
1550                ("artifactResolutionServiceUrl", ""),
1551                ("attributeConsumingServiceIndex", "0"),
1552                ("attributeConsumingServiceName", ""),
1553                ("authnContextComparisonType", "exact"),
1554                ("backchannelSupported", "false"),
1555                ("caseSensitiveOriginalUsername", "false"),
1556                ("encryptionAlgorithm", "RSA-OAEP"),
1557                ("forceAuthn", "false"),
1558                ("guiOrder", ""),
1559                ("principalAttribute", principal_attribute),
1560                ("principalType", "ATTRIBUTE"),
1561                ("sendClientIdOnLogout", "false"),
1562                ("sendIdTokenOnLogout", "true"),
1563                ("signSpMetadata", "false"),
1564                ("signatureAlgorithm", "RSA_SHA256"),
1565                ("singleLogoutServiceUrl", ""),
1566                ("syncMode", "LEGACY"),
1567                ("useMetadataDescriptorUrl", "true"),
1568                ("validateSignature", "true"),
1569                ("wantAssertionsEncrypted", "true"),
1570                ("wantAssertionsSigned", "true"),
1571                ("wantAuthnRequestsSigned", "true"),
1572                ("xmlSigKeyInfoKeyNameTransformer", "KEY_ID"),
1573            ]
1574            .map(|(k, v)| (k.to_string(), v.to_string())),
1575        );
1576    }
1577    idp
1578}