diff --git a/src/keycloak/mod.rs b/src/keycloak/mod.rs index d982c71..11b8f9a 100644 --- a/src/keycloak/mod.rs +++ b/src/keycloak/mod.rs @@ -20,7 +20,7 @@ pub struct KeycloakAPI<'a> { } #[derive(Debug, thiserror::Error)] -pub enum GetCredentialsError { +pub enum GetUserIdError { #[error("on request: {0}")] Fetch(#[from] curl::Error), #[error("unexpected API response: {0}")] @@ -31,6 +31,16 @@ pub enum GetCredentialsError { ErrorResponse, } +#[derive(Debug, thiserror::Error)] +pub enum GetCredentialsError { + #[error("on request: {0}")] + Fetch(#[from] curl::Error), + #[error("unexpected API response: {0}")] + InvalidAPIResponse(#[from] serde_json::Error), + #[error("server error response")] + ErrorResponse, +} + #[derive(Debug, thiserror::Error)] pub enum SetCredentialsError { #[error("on request: {0}")] @@ -143,11 +153,11 @@ impl<'a> KeycloakAPI<'a> { } } - pub fn get_user_credentials( + pub fn get_user_id( &self, username: &str, admin_token: &str, - ) -> Result<(String, Vec), GetCredentialsError> { + ) -> Result { let Response { status, data: body } = self.curl.request( Method::GET, &format!( @@ -158,20 +168,28 @@ impl<'a> KeycloakAPI<'a> { )?; if !status.is_ok() { err_http_body!(if self.debug => &body); - return Err(GetCredentialsError::ErrorResponse); + return Err(GetUserIdError::ErrorResponse); } let found_users: Vec = serde_json::from_slice(&body)?; debug_info!(if self.debug => "matched users" = found_users); let Ok([first_user]) = TryInto::<[api::User; _]>::try_into(found_users) else { - return Err(GetCredentialsError::NonUnitResults); + return Err(GetUserIdError::NonUnitResults); }; + Ok(first_user.id) + } + + pub fn get_user_credentials( + &self, + user_id: &str, + admin_token: &str, + ) -> Result, GetCredentialsError> { let Response { status, data: body } = self.curl.request( Method::GET, &format!( "{}/admin/realms/{}/users/{}/credentials", - self.config.base_url, self.config.default_realm, first_user.id + self.config.base_url, self.config.default_realm, user_id ), &[ &headers::authorization_bearer!(admin_token), @@ -183,7 +201,7 @@ impl<'a> KeycloakAPI<'a> { return Err(GetCredentialsError::ErrorResponse); } - Ok((first_user.id, serde_json::from_slice(&body)?)) + Ok(serde_json::from_slice(&body)?) } pub fn set_user_credentials( diff --git a/src/lib.rs b/src/lib.rs index 979485a..6270978 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,8 +58,8 @@ impl PamModule for KeycloakPAM { /// /// # Errors /// - /// As a baseline, the errors of [`Self::init`], [`Self::get_token_for`] and - /// [`Self::get_creds_for`]. + /// As a baseline, the errors of [`Self::init`], [`Self::get_token_for`], [`Self::get_id_for`] + /// and [`Self::get_creds_for`]. /// /// Extra: /// - [`E::BufferError`]: If some data could not be converted to UTF-8 or anything went wrong @@ -103,18 +103,32 @@ impl PamModule for KeycloakPAM { }, )?; - debug_print!(if args.debug => "verifying credentials"); - let (user_id, user_creds) = Self::get_creds_for( + let user_id = Self::get_id_for( &api_client, username_str, &admin_token.access_token, can_print, )?; - if !user_creds.is_empty() { - let old_authtok = handle.old_authtok(None)?; - Self::get_token_for(&api_client, &username, &old_authtok, can_print)?; - debug_print!(if args.debug => "identity verified"); + let skip_verification = libc::is_root_ruid(); + + if skip_verification { + debug_print!(if args.debug => "authtok coming from root ruid, \ + skipping verification..."); + } else { + debug_print!(if args.debug => "verifying credentials"); + let user_creds = Self::get_creds_for( + &api_client, + &user_id, + &admin_token.access_token, + can_print, + )?; + + if !user_creds.is_empty() { + let old_authtok = handle.old_authtok(None)?; + Self::get_token_for(&api_client, &username, &old_authtok, can_print)?; + debug_print!(if args.debug => "identity verified"); + } } let authtok = handle.authtok(None)?; @@ -209,26 +223,46 @@ impl KeycloakPAM { }) } + /// # Errors + /// + /// - [`E::AuthInfoUnavailable`]: Anything wrong with keycloak, we assume it's just down now. + /// - [`E::CredentialsError`]: If username matched other than one user. + /// - [`E::ServiceError`]: If got an unexpected error response. + fn get_id_for( + api_client: &keycloak::KeycloakAPI, + username: &str, + admin_token: &str, + can_print: bool, + ) -> nonstick::Result { + error_print!(api_client.get_user_id(username, admin_token), can_print).map_err(|error| { + match error { + keycloak::GetUserIdError::Fetch(_) + | keycloak::GetUserIdError::InvalidAPIResponse(_) => E::AuthInfoUnavailable, + keycloak::GetUserIdError::NonUnitResults => E::CredentialsError, + keycloak::GetUserIdError::ErrorResponse => E::ServiceError, + } + }) + } + /// Can be used to see if user has configured passwords. /// /// # Errors /// /// - [`E::AuthInfoUnavailable`]: Anything wrong with keycloak, we assume it's just down now. - /// - [`E::AuthenticationError`]: If everything went right but checking credentials. + /// - [`E::ServiceError`]: If got an unexpected error response. fn get_creds_for( api_client: &keycloak::KeycloakAPI, - username: &str, + user_id: &str, admin_token: &str, can_print: bool, - ) -> nonstick::Result<(String, Vec)> { + ) -> nonstick::Result> { error_print!( - api_client.get_user_credentials(username, admin_token), + api_client.get_user_credentials(user_id, admin_token), can_print ) .map_err(|error| match error { keycloak::GetCredentialsError::Fetch(_) | keycloak::GetCredentialsError::InvalidAPIResponse(_) => E::AuthInfoUnavailable, - keycloak::GetCredentialsError::NonUnitResults => E::CredentialsError, keycloak::GetCredentialsError::ErrorResponse => E::ServiceError, }) } diff --git a/src/libc.rs b/src/libc.rs index 4e575bd..9552faf 100644 --- a/src/libc.rs +++ b/src/libc.rs @@ -13,6 +13,12 @@ pub enum GetgrnamError { Unexpected, } +#[must_use] +pub fn is_root_ruid() -> bool { + // SAFETY: `getuid` has no side effects or thread safety concerns, just gets the number + 0 == unsafe { libc::getuid() } +} + /// # Safety /// /// The function is completely safe to call but the use of `cb` requires unsafe: