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#[derive(Debug, serde::Deserialize, serde::Serialize)]
24pub struct ServerInfo {
25 #[serde(default)]
27 pub realm: Option<String>,
28}
29
30#[derive(Debug, serde::Deserialize, serde::Serialize)]
32pub struct RealmInfo {
33 #[serde(default)]
35 pub realm: Option<String>,
36 #[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#[derive(Default)]
67pub struct KeycloakBuilder {
68 no_refresh: bool,
69 env_prefix: Option<&'static str>,
70 config: Option<KeycloakConfig>,
71}
72
73impl KeycloakBuilder {
74 pub fn with_no_refresh(mut self) -> Self {
76 self.no_refresh = true;
77 self
78 }
79
80 pub fn with_env_prefix(mut self, prefix: &'static str) -> Self {
82 self.env_prefix = Some(prefix);
83 self
84 }
85
86 pub fn with_config(mut self, config: KeycloakConfig) -> Self {
88 self.config = Some(config);
89 self
90 }
91
92 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#[derive(Clone)]
128pub struct Keycloak {
129 inner: Arc<Inner>,
130}
131
132impl Keycloak {
133 pub fn builder() -> KeycloakBuilder {
135 KeycloakBuilder::default()
136 }
137
138 pub fn http_client(&self) -> &reqwest::Client {
140 &self.inner.client
141 }
142
143 pub async fn new() -> anyhow::Result<Self> {
145 KeycloakBuilder::default().build().await
146 }
147
148 pub fn app_urls(&self) -> Vec<&str> {
150 self.inner.config.app_urls()
151 }
152
153 pub fn public_url(&self) -> &str {
155 self.inner.config.public_url()
156 }
157
158 pub fn config(&self) -> &KeycloakConfig {
160 &self.inner.config
161 }
162
163 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 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 pub async fn remove_realm(&self, realm: &str) -> Result<(), KeycloakError> {
204 self.inner.admin.realm(realm).delete().await.map(|_| ())
205 }
206
207 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
1540pub 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}