use std::convert::TryFrom;
use std::fs;
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use kanidm_proto::internal::{
ApiToken, AppLink, BackupCodesView, CURequest, CUSessionToken, CUStatus, CredentialStatus,
IdentifyUserRequest, IdentifyUserResponse, ImageValue, OperationError, RadiusAuthToken,
SearchRequest, SearchResponse, UserAuthToken,
};
use kanidm_proto::v1::{
AuthIssueSession, AuthRequest, Entry as ProtoEntry, UatStatus, UnixGroupToken, UnixUserToken,
WhoamiResponse,
};
use kanidmd_lib::idm::identityverification::{
IdentifyUserDisplayCodeEvent, IdentifyUserStartEvent, IdentifyUserSubmitCodeEvent,
};
use ldap3_proto::simple::*;
use regex::Regex;
use tracing::{error, info, instrument, trace};
use uuid::Uuid;
use compact_jwt::{JweCompact, Jwk, JwsCompact};
use kanidmd_lib::be::BackendTransaction;
use kanidmd_lib::prelude::*;
use kanidmd_lib::{
event::{OnlineBackupEvent, SearchEvent, SearchResult, WhoamiResult},
filter::{Filter, FilterInvalid},
idm::account::ListUserAuthTokenEvent,
idm::credupdatesession::CredentialUpdateSessionToken,
idm::event::{
AuthEvent, AuthResult, CredentialStatusEvent, RadiusAuthTokenEvent, ReadBackupCodeEvent,
UnixGroupTokenEvent, UnixUserAuthEvent, UnixUserTokenEvent,
},
idm::ldap::{LdapBoundToken, LdapResponseState},
idm::oauth2::{
AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AuthorisationRequest,
AuthoriseResponse, JwkKeySet, Oauth2Error, Oauth2Rfc8414MetadataResponse,
OidcDiscoveryResponse, OidcToken,
},
idm::server::{DomainInfoRead, IdmServerTransaction},
idm::serviceaccount::ListApiTokenEvent,
idm::ClientAuthInfo,
};
use super::QueryServerReadV1;
impl QueryServerReadV1 {
#[instrument(
level = "info",
name = "search",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_search(
&self,
client_auth_info: ClientAuthInfo,
req: SearchRequest,
eventid: Uuid,
) -> Result<SearchResponse, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!(?e, "Invalid identity");
e
})?;
let search =
SearchEvent::from_message(ident, &req, &mut idms_prox_read.qs_read).map_err(|e| {
error!(?e, "Failed to begin search");
e
})?;
trace!(?search, "Begin event");
let entries = idms_prox_read.qs_read.search_ext(&search)?;
SearchResult::new(&mut idms_prox_read.qs_read, &entries).map(SearchResult::response)
}
#[instrument(
level = "info",
name = "auth",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_auth(
&self,
sessionid: Option<Uuid>,
req: AuthRequest,
eventid: Uuid,
client_auth_info: ClientAuthInfo,
) -> Result<AuthResult, OperationError> {
let ct = duration_from_epoch_now();
let mut idm_auth = self.idms.auth().await?;
security_info!(?sessionid, ?req, "Begin auth event");
let ae = AuthEvent::from_message(sessionid, req).map_err(|e| {
error!(err = ?e, "Failed to parse AuthEvent");
e
})?;
idm_auth.expire_auth_sessions(ct).await;
let res = idm_auth
.auth(&ae, ct, client_auth_info)
.await
.and_then(|r| idm_auth.commit().map(|_| r));
security_info!(?res, "Sending auth result");
res
}
#[instrument(
level = "info",
name = "reauth",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_reauth(
&self,
client_auth_info: ClientAuthInfo,
issue: AuthIssueSession,
eventid: Uuid,
) -> Result<AuthResult, OperationError> {
let ct = duration_from_epoch_now();
let mut idm_auth = self.idms.auth().await?;
security_info!("Begin reauth event");
let ident = idm_auth
.validate_client_auth_info_to_ident(client_auth_info.clone(), ct)
.map_err(|e| {
error!(?e, "Invalid identity");
e
})?;
idm_auth.expire_auth_sessions(ct).await;
let res = idm_auth
.reauth_init(ident, issue, ct, client_auth_info)
.await
.and_then(|r| idm_auth.commit().map(|_| r));
security_info!(?res, "Sending reauth result");
res
}
#[instrument(
level = "info",
name = "online_backup",
skip_all,
fields(uuid = ?msg.eventid)
)]
pub async fn handle_online_backup(
&self,
msg: OnlineBackupEvent,
outpath: &str,
versions: usize,
) -> Result<(), OperationError> {
trace!(eventid = ?msg.eventid, "Begin online backup event");
let now = time::OffsetDateTime::now_utc();
#[allow(clippy::unwrap_used)]
let timestamp = now.format(&Rfc3339).unwrap();
let dest_file = format!("{}/backup-{}.json", outpath, timestamp);
if Path::new(&dest_file).exists() {
error!(
"Online backup file {} already exists, will not overwrite it.",
dest_file
);
return Err(OperationError::InvalidState);
}
{
let mut idms_prox_read = self.idms.proxy_read().await?;
idms_prox_read
.qs_read
.get_be_txn()
.backup(&dest_file)
.map(|()| {
info!("Online backup created {} successfully", dest_file);
})
.map_err(|e| {
error!("Online backup failed to create {}: {:?}", dest_file, e);
OperationError::InvalidState
})?;
}
let re = Regex::new(r"^backup-\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?Z\.json$")
.map_err(|error| {
error!(
"Failed to parse regexp for online backup files: {:?}",
error
);
OperationError::InvalidState
})?;
let mut backup_file_list: Vec<PathBuf> = Vec::new();
match fs::read_dir(outpath) {
Ok(rd) => {
for entry in rd {
let pb = entry
.map_err(|e| {
error!(?e, "Pathbuf access");
OperationError::InvalidState
})?
.path();
if !pb.is_file() {
continue;
}
let file_name = pb.file_name().and_then(|f| f.to_str()).ok_or_else(|| {
error!("filename is invalid");
OperationError::InvalidState
})?;
if re.is_match(file_name) {
backup_file_list.push(pb.clone());
}
}
}
Err(e) => {
error!("Online backup cleanup error read dir {}: {}", outpath, e);
return Err(OperationError::InvalidState);
}
}
backup_file_list.sort();
if backup_file_list.len() > versions {
let x = backup_file_list.len() - versions;
info!(
"Online backup cleanup found {} versions, should keep {}, will remove {}",
backup_file_list.len(),
versions,
x
);
backup_file_list.truncate(x);
for file in backup_file_list {
debug!("Online backup cleanup: removing {:?}", &file);
match fs::remove_file(&file) {
Ok(_) => {}
Err(e) => {
error!(
"Online backup cleanup failed to remove file {:?}: {:?}",
file, e
)
}
};
}
} else {
debug!("Online backup cleanup had no files to remove");
};
Ok(())
}
#[instrument(
level = "info",
name = "whoami",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_whoami(
&self,
client_auth_info: ClientAuthInfo,
eventid: Uuid,
) -> Result<WhoamiResponse, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!(?e, "Invalid identity");
e
})?;
let srch =
SearchEvent::from_whoami_request(ident, &idms_prox_read.qs_read).map_err(|e| {
error!(?e, "Failed to begin whoami");
e
})?;
trace!(search = ?srch, "Begin event");
let mut entries = idms_prox_read.qs_read.search_ext(&srch)?;
match entries.pop() {
Some(e) if entries.is_empty() => {
WhoamiResult::new(&mut idms_prox_read.qs_read, &e).map(WhoamiResult::response)
}
Some(_) => Err(OperationError::InvalidState), _ => Err(OperationError::NoMatchingEntries),
}
}
#[instrument(
level = "info",
name = "whoami_uat",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_whoami_uat(
&self,
client_auth_info: ClientAuthInfo,
eventid: Uuid,
) -> Result<UserAuthToken, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
idms_prox_read
.validate_client_auth_info_to_uat(client_auth_info, ct)
.map_err(|e| {
error!(?e, "Invalid identity");
e
})
}
#[instrument(level = "debug", skip_all)]
pub async fn handle_oauth2_rs_image_get_image(
&self,
client_auth_info: ClientAuthInfo,
rs: Filter<FilterInvalid>,
) -> Result<Option<ImageValue>, OperationError> {
let mut idms_prox_read = self.idms.proxy_read().await?;
let ct = duration_from_epoch_now();
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!(err = ?e, "Invalid identity in handle_oauth2_rs_image_get_image");
e
})?;
let attrs = vec![Attribute::Image.to_string()];
let search = SearchEvent::from_internal_message(
ident,
&rs,
Some(attrs.as_slice()),
&mut idms_prox_read.qs_read,
)?;
let entries = idms_prox_read.qs_read.search(&search)?;
Ok(entries
.first()
.and_then(|entry| entry.get_ava_single_image(Attribute::Image)))
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_internalsearch(
&self,
client_auth_info: ClientAuthInfo,
filter: Filter<FilterInvalid>,
attrs: Option<Vec<String>>,
eventid: Uuid,
) -> Result<Vec<ProtoEntry>, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let srch = match SearchEvent::from_internal_message(
ident,
&filter,
attrs.as_deref(),
&mut idms_prox_read.qs_read,
) {
Ok(s) => s,
Err(e) => {
error!("Failed to begin internal api search: {:?}", e);
return Err(e);
}
};
trace!(?srch, "Begin event");
match idms_prox_read.qs_read.search_ext(&srch) {
Ok(entries) => SearchResult::new(&mut idms_prox_read.qs_read, &entries)
.map(|ok_sr| ok_sr.into_proto_array()),
Err(e) => Err(e),
}
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_search_refers(
&self,
client_auth_info: ClientAuthInfo,
filter: Filter<FilterInvalid>,
uuid_or_name: String,
attrs: Option<Vec<String>>,
eventid: Uuid,
) -> Result<Vec<ProtoEntry>, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let target_uuid = idms_prox_read
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.inspect_err(|err| {
error!(?err, "Error resolving id to target");
})?;
let filter = Filter::join_parts_and(
filter,
filter_all!(f_eq(Attribute::Refers, PartialValue::Refer(target_uuid))),
);
let srch = match SearchEvent::from_internal_message(
ident,
&filter,
attrs.as_deref(),
&mut idms_prox_read.qs_read,
) {
Ok(s) => s,
Err(e) => {
error!("Failed to begin internal api search: {:?}", e);
return Err(e);
}
};
trace!(?srch, "Begin event");
match idms_prox_read.qs_read.search_ext(&srch) {
Ok(entries) => SearchResult::new(&mut idms_prox_read.qs_read, &entries)
.map(|ok_sr| ok_sr.into_proto_array()),
Err(e) => Err(e),
}
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_internalsearchrecycled(
&self,
client_auth_info: ClientAuthInfo,
filter: Filter<FilterInvalid>,
attrs: Option<Vec<String>>,
eventid: Uuid,
) -> Result<Vec<ProtoEntry>, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let srch = match SearchEvent::from_internal_recycle_message(
ident,
&filter,
attrs.as_deref(),
&idms_prox_read.qs_read,
) {
Ok(s) => s,
Err(e) => {
error!("Failed to begin recycled search: {:?}", e);
return Err(e);
}
};
trace!(?srch, "Begin event");
match idms_prox_read.qs_read.search_ext(&srch) {
Ok(entries) => SearchResult::new(&mut idms_prox_read.qs_read, &entries)
.map(|ok_sr| ok_sr.into_proto_array()),
Err(e) => Err(e),
}
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_internalradiusread(
&self,
client_auth_info: ClientAuthInfo,
uuid_or_name: String,
eventid: Uuid,
) -> Result<Option<String>, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let target_uuid = idms_prox_read
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.inspect_err(|err| {
error!(?err, "Error resolving id to target");
})?;
let srch = match SearchEvent::from_target_uuid_request(
ident,
target_uuid,
&idms_prox_read.qs_read,
) {
Ok(s) => s,
Err(e) => {
error!("Failed to begin radius read: {:?}", e);
return Err(e);
}
};
trace!(?srch, "Begin event");
match idms_prox_read.qs_read.search_ext(&srch) {
Ok(mut entries) => {
let r = entries
.pop()
.and_then(|entry| {
entry
.get_ava_single(Attribute::RadiusSecret)
.and_then(|v| v.get_secret_str().map(str::to_string))
});
Ok(r)
}
Err(e) => Err(e),
}
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_internalradiustokenread(
&self,
client_auth_info: ClientAuthInfo,
uuid_or_name: String,
eventid: Uuid,
) -> Result<RadiusAuthToken, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let target_uuid = idms_prox_read
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.inspect_err(|err| {
error!(?err, "Error resolving id to target");
})?;
let rate = match RadiusAuthTokenEvent::from_parts(
ident,
target_uuid,
) {
Ok(s) => s,
Err(e) => {
error!("Failed to begin radius token read: {:?}", e);
return Err(e);
}
};
trace!(?rate, "Begin event");
idms_prox_read.get_radiusauthtoken(&rate, ct)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_internalunixusertokenread(
&self,
client_auth_info: ClientAuthInfo,
uuid_or_name: String,
eventid: Uuid,
) -> Result<UnixUserToken, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let target_uuid = idms_prox_read
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.map_err(|e| {
let uuid_or_name_val = match uuid_or_name.is_empty() {
true => "<empty uuid_or_name>",
false => &uuid_or_name,
};
admin_info!(
err = ?e,
"Error resolving {} as gidnumber continuing ...",
uuid_or_name_val
);
e
})?;
let rate = match UnixUserTokenEvent::from_parts(ident, target_uuid) {
Ok(s) => s,
Err(e) => {
error!("Failed to begin unix token read: {:?}", e);
return Err(e);
}
};
trace!(?rate, "Begin event");
idms_prox_read.get_unixusertoken(&rate, ct)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_internalunixgrouptokenread(
&self,
client_auth_info: ClientAuthInfo,
uuid_or_name: String,
eventid: Uuid,
) -> Result<UnixGroupToken, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let target_uuid = idms_prox_read
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.map_err(|e| {
admin_info!(err = ?e, "Error resolving as gidnumber continuing");
e
})?;
let rate = match UnixGroupTokenEvent::from_parts(
ident,
target_uuid,
) {
Ok(s) => s,
Err(e) => {
error!("Failed to begin unix group token read: {:?}", e);
return Err(e);
}
};
trace!(?rate, "Begin event");
idms_prox_read.get_unixgrouptoken(&rate)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_internalsshkeyread(
&self,
client_auth_info: ClientAuthInfo,
uuid_or_name: String,
eventid: Uuid,
) -> Result<Vec<String>, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let target_uuid = idms_prox_read
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.inspect_err(|err| {
error!(?err, "Error resolving id to target");
})?;
let srch = match SearchEvent::from_target_uuid_request(
ident,
target_uuid,
&idms_prox_read.qs_read,
) {
Ok(s) => s,
Err(e) => {
error!("Failed to begin ssh key read: {:?}", e);
return Err(e);
}
};
trace!(?srch, "Begin event");
match idms_prox_read.qs_read.search_ext(&srch) {
Ok(mut entries) => {
let r = entries
.pop()
.and_then(|e| {
e.get_ava_iter_sshpubkeys(Attribute::SshPublicKey)
.map(|i| i.collect())
})
.unwrap_or_else(|| {
Vec::new()
});
Ok(r)
}
Err(e) => Err(e),
}
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_internalsshkeytagread(
&self,
client_auth_info: ClientAuthInfo,
uuid_or_name: String,
tag: String,
eventid: Uuid,
) -> Result<Option<String>, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let target_uuid = idms_prox_read
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.inspect_err(|err| {
admin_info!(?err, "Error resolving id to target");
})?;
let srch = match SearchEvent::from_target_uuid_request(
ident,
target_uuid,
&idms_prox_read.qs_read,
) {
Ok(s) => s,
Err(e) => {
error!("Failed to begin sshkey tag read: {:?}", e);
return Err(e);
}
};
trace!(?srch, "Begin event");
match idms_prox_read.qs_read.search_ext(&srch) {
Ok(mut entries) => {
let r = entries
.pop()
.map(|e| {
e.get_ava_set(Attribute::SshPublicKey).and_then(|vs| {
vs.get_ssh_tag(&tag).map(|pk| pk.to_string())
})
})
.unwrap_or_else(|| {
None
});
Ok(r)
}
Err(e) => Err(e),
}
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_service_account_api_token_get(
&self,
client_auth_info: ClientAuthInfo,
uuid_or_name: String,
eventid: Uuid,
) -> Result<Vec<ApiToken>, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let target = idms_prox_read
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.inspect_err(|err| {
error!(?err, "Error resolving id to target");
})?;
let lte = ListApiTokenEvent { ident, target };
idms_prox_read.service_account_list_api_token(<e)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_account_user_auth_token_get(
&self,
client_auth_info: ClientAuthInfo,
uuid_or_name: String,
eventid: Uuid,
) -> Result<Vec<UatStatus>, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let target = idms_prox_read
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.inspect_err(|err| {
error!(?err, "Error resolving id to target");
})?;
let lte = ListUserAuthTokenEvent { ident, target };
idms_prox_read.account_list_user_auth_tokens(<e)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_user_identity_verification(
&self,
client_auth_info: ClientAuthInfo,
eventid: Uuid,
user_request: IdentifyUserRequest,
other_id: String,
) -> Result<IdentifyUserResponse, OperationError> {
trace!("{:?}", &user_request);
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let target = idms_prox_read
.qs_read
.name_to_uuid(&other_id)
.map_err(|e| {
error!("No user found with the provided ID: {:?}", e);
e
})?;
match user_request {
IdentifyUserRequest::Start => idms_prox_read
.handle_identify_user_start(&IdentifyUserStartEvent::new(target, ident)),
IdentifyUserRequest::DisplayCode => idms_prox_read.handle_identify_user_display_code(
&IdentifyUserDisplayCodeEvent::new(target, ident),
),
IdentifyUserRequest::SubmitCode { other_totp } => idms_prox_read
.handle_identify_user_submit_code(&IdentifyUserSubmitCodeEvent::new(
target, ident, other_totp,
)),
}
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_idmaccountunixauth(
&self,
client_auth_info: ClientAuthInfo,
uuid_or_name: String,
cred: String,
eventid: Uuid,
) -> Result<Option<UnixUserToken>, OperationError> {
let ct = duration_from_epoch_now();
let mut idm_auth = self.idms.auth().await?;
let ident = idm_auth
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!(err = ?e, "Invalid identity");
e
})?;
let target_uuid = idm_auth
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.map_err(|e| {
admin_info!(err = ?e, "Error resolving as gidnumber continuing");
e
})?;
let uuae = match UnixUserAuthEvent::from_parts(ident, target_uuid, cred) {
Ok(s) => s,
Err(e) => {
error!(err = ?e, "Failed to begin unix auth");
return Err(e);
}
};
security_info!(event = ?uuae, "Begin unix auth event");
let res = idm_auth
.auth_unix(&uuae, ct)
.await
.and_then(|r| idm_auth.commit().map(|_| r));
security_info!(?res, "Sending result");
res
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_idmcredentialstatus(
&self,
client_auth_info: ClientAuthInfo,
uuid_or_name: String,
eventid: Uuid,
) -> Result<CredentialStatus, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!(err = ?e, "Invalid identity");
e
})?;
let target_uuid = idms_prox_read
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.map_err(|e| {
error!(err = ?e, "Error resolving id to target");
e
})?;
let cse = match CredentialStatusEvent::from_parts(
ident,
target_uuid,
) {
Ok(s) => s,
Err(e) => {
error!(err = ?e, "Failed to begin credential status read");
return Err(e);
}
};
trace!(?cse, "Begin event");
idms_prox_read.get_credentialstatus(&cse)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_idmbackupcodeview(
&self,
client_auth_info: ClientAuthInfo,
uuid_or_name: String,
eventid: Uuid,
) -> Result<BackupCodesView, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let target_uuid = idms_prox_read
.qs_read
.name_to_uuid(uuid_or_name.as_str())
.inspect_err(|err| {
error!(?err, "Error resolving id to target");
})?;
let rbce = match ReadBackupCodeEvent::from_parts(
ident,
target_uuid,
) {
Ok(s) => s,
Err(e) => {
error!("Failed to begin backup code read: {:?}", e);
return Err(e);
}
};
trace!(?rbce, "Begin event");
idms_prox_read.get_backup_codes(&rbce)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_idmcredentialupdatestatus(
&self,
session_token: CUSessionToken,
eventid: Uuid,
) -> Result<CUStatus, OperationError> {
let session_token = JweCompact::from_str(&session_token.token)
.map(|token_enc| CredentialUpdateSessionToken { token_enc })
.map_err(|err| {
error!(?err, "malformed token");
OperationError::InvalidRequestState
})?;
let ct = duration_from_epoch_now();
let idms_cred_update = self.idms.cred_update_transaction().await?;
idms_cred_update
.credential_update_status(&session_token, ct)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_update_status",
);
e
})
.map(|sta| sta.into())
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_idmcredentialupdate(
&self,
session_token: CUSessionToken,
scr: CURequest,
eventid: Uuid,
) -> Result<CUStatus, OperationError> {
let session_token = JweCompact::from_str(&session_token.token)
.map(|token_enc| CredentialUpdateSessionToken { token_enc })
.map_err(|err| {
error!(?err, "Invalid Token - Must be a compact JWE");
OperationError::InvalidRequestState
})?;
let ct = duration_from_epoch_now();
let idms_cred_update = self.idms.cred_update_transaction().await?;
debug!(?scr);
match scr {
CURequest::PrimaryRemove => idms_cred_update
.credential_primary_delete(&session_token, ct)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_primary_delete",
);
e
}),
CURequest::Password(pw) => idms_cred_update
.credential_primary_set_password(&session_token, ct, &pw)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_primary_set_password",
);
e
}),
CURequest::CancelMFAReg => idms_cred_update
.credential_update_cancel_mfareg(&session_token, ct)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_update_cancel_mfareg",
);
e
}),
CURequest::TotpGenerate => idms_cred_update
.credential_primary_init_totp(&session_token, ct)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_primary_init_totp",
);
e
}),
CURequest::TotpVerify(totp_chal, label) => idms_cred_update
.credential_primary_check_totp(&session_token, ct, totp_chal, &label)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_primary_check_totp",
);
e
}),
CURequest::TotpAcceptSha1 => idms_cred_update
.credential_primary_accept_sha1_totp(&session_token, ct)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_primary_accept_sha1_totp",
);
e
}),
CURequest::TotpRemove(label) => idms_cred_update
.credential_primary_remove_totp(&session_token, ct, &label)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_primary_remove_totp",
);
e
}),
CURequest::BackupCodeGenerate => idms_cred_update
.credential_primary_init_backup_codes(&session_token, ct)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_primary_init_backup_codes",
);
e
}),
CURequest::BackupCodeRemove => idms_cred_update
.credential_primary_remove_backup_codes(&session_token, ct)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_primary_remove_backup_codes",
);
e
}),
CURequest::PasskeyInit => idms_cred_update
.credential_passkey_init(&session_token, ct)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_passkey_init",
);
e
}),
CURequest::PasskeyFinish(label, rpkc) => idms_cred_update
.credential_passkey_finish(&session_token, ct, label, &rpkc)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_passkey_finish",
);
e
}),
CURequest::PasskeyRemove(uuid) => idms_cred_update
.credential_passkey_remove(&session_token, ct, uuid)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_passkey_remove"
);
e
}),
CURequest::AttestedPasskeyInit => idms_cred_update
.credential_attested_passkey_init(&session_token, ct)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_attested_passkey_init"
);
e
}),
CURequest::AttestedPasskeyFinish(label, rpkc) => idms_cred_update
.credential_attested_passkey_finish(&session_token, ct, label, &rpkc)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_attested_passkey_finish"
);
e
}),
CURequest::AttestedPasskeyRemove(uuid) => idms_cred_update
.credential_attested_passkey_remove(&session_token, ct, uuid)
.map_err(|e| {
error!(
err = ?e,
"Failed to begin credential_attested_passkey_remove"
);
e
}),
CURequest::UnixPasswordRemove => idms_cred_update
.credential_unix_delete(&session_token, ct)
.inspect_err(|err| {
error!(?err, "Failed to begin credential_unix_delete");
}),
CURequest::UnixPassword(pw) => idms_cred_update
.credential_unix_set_password(&session_token, ct, &pw)
.inspect_err(|err| {
error!(?err, "Failed to begin credential_unix_set_password");
}),
CURequest::SshPublicKey(label, pubkey) => idms_cred_update
.credential_sshkey_add(&session_token, ct, label, pubkey)
.inspect_err(|err| {
error!(?err, "Failed to begin credential_sshkey_remove");
}),
CURequest::SshPublicKeyRemove(label) => idms_cred_update
.credential_sshkey_remove(&session_token, ct, &label)
.inspect_err(|err| {
error!(?err, "Failed to begin credential_sshkey_remove");
}),
}
.map(|sta| sta.into())
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_oauth2_basic_secret_read(
&self,
client_auth_info: ClientAuthInfo,
filter: Filter<FilterInvalid>,
eventid: Uuid,
) -> Result<Option<String>, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
let srch = match SearchEvent::from_internal_message(
ident,
&filter,
None,
&mut idms_prox_read.qs_read,
) {
Ok(s) => s,
Err(e) => {
error!("Failed to begin oauth2 basic secret read: {:?}", e);
return Err(e);
}
};
trace!(?srch, "Begin event");
match idms_prox_read.qs_read.search_ext(&srch) {
Ok(mut entries) => {
let r = entries
.pop()
.and_then(|entry| {
entry
.get_ava_single(Attribute::OAuth2RsBasicSecret)
.and_then(|v| v.get_secret_str().map(str::to_string))
});
Ok(r)
}
Err(e) => Err(e),
}
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_oauth2_authorise(
&self,
client_auth_info: ClientAuthInfo,
auth_req: AuthorisationRequest,
eventid: Uuid,
) -> Result<AuthoriseResponse, Oauth2Error> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self
.idms
.proxy_read()
.await
.map_err(Oauth2Error::ServerError)?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.inspect_err(|e| {
error!("Invalid identity: {:?}", e);
})
.ok();
idms_prox_read.check_oauth2_authorisation(ident.as_ref(), &auth_req, ct)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_oauth2_authorise_reject(
&self,
client_auth_info: ClientAuthInfo,
consent_req: String,
eventid: Uuid,
) -> Result<Url, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
idms_prox_read.check_oauth2_authorise_reject(&ident, &consent_req, ct)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_oauth2_token_introspect(
&self,
client_auth_info: ClientAuthInfo,
intr_req: AccessTokenIntrospectRequest,
eventid: Uuid,
) -> Result<AccessTokenIntrospectResponse, Oauth2Error> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self
.idms
.proxy_read()
.await
.map_err(Oauth2Error::ServerError)?;
idms_prox_read.check_oauth2_token_introspect(&client_auth_info, &intr_req, ct)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_oauth2_openid_userinfo(
&self,
client_id: String,
token: JwsCompact,
eventid: Uuid,
) -> Result<OidcToken, Oauth2Error> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self
.idms
.proxy_read()
.await
.map_err(Oauth2Error::ServerError)?;
idms_prox_read.oauth2_openid_userinfo(&client_id, token, ct)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_oauth2_openid_discovery(
&self,
client_id: String,
eventid: Uuid,
) -> Result<OidcDiscoveryResponse, OperationError> {
let idms_prox_read = self.idms.proxy_read().await?;
idms_prox_read.oauth2_openid_discovery(&client_id)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_oauth2_rfc8414_metadata(
&self,
client_id: String,
eventid: Uuid,
) -> Result<Oauth2Rfc8414MetadataResponse, OperationError> {
let idms_prox_read = self.idms.proxy_read().await?;
idms_prox_read.oauth2_rfc8414_metadata(&client_id)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_oauth2_openid_publickey(
&self,
client_id: String,
eventid: Uuid,
) -> Result<JwkKeySet, OperationError> {
let idms_prox_read = self.idms.proxy_read().await?;
idms_prox_read.oauth2_openid_publickey(&client_id)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_list_applinks(
&self,
client_auth_info: ClientAuthInfo,
eventid: Uuid,
) -> Result<Vec<AppLink>, OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
let ident = idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})?;
idms_prox_read.list_applinks(&ident)
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_auth_valid(
&self,
client_auth_info: ClientAuthInfo,
eventid: Uuid,
) -> Result<(), OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_read = self.idms.proxy_read().await?;
idms_prox_read
.validate_client_auth_info_to_ident(client_auth_info, ct)
.map(|_| ())
.map_err(|e| {
error!("Invalid identity: {:?}", e);
e
})
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_public_jwk_get(
&self,
key_id: String,
eventid: Uuid,
) -> Result<Jwk, OperationError> {
let mut idms_prox_read = self.idms.proxy_read().await?;
idms_prox_read.jws_public_jwk(key_id.as_str())
}
#[instrument(
level = "info",
skip_all,
fields(uuid = ?eventid)
)]
pub async fn handle_ldaprequest(
&self,
eventid: Uuid,
protomsg: LdapMsg,
uat: Option<LdapBoundToken>,
ip_addr: IpAddr,
) -> Option<LdapResponseState> {
let res = match ServerOps::try_from(protomsg) {
Ok(server_op) => self
.ldap
.do_op(&self.idms, server_op, uat, ip_addr, eventid)
.await
.unwrap_or_else(|e| {
error!("do_op failed -> {:?}", e);
LdapResponseState::Disconnect(DisconnectionNotice::gen(
LdapResultCode::Other,
format!("Internal Server Error {:?}", &eventid).as_str(),
))
}),
Err(_) => LdapResponseState::Disconnect(DisconnectionNotice::gen(
LdapResultCode::ProtocolError,
format!("Invalid Request {:?}", &eventid).as_str(),
)),
};
Some(res)
}
pub fn domain_info_read(&self) -> DomainInfoRead {
self.idms.domain_read()
}
}