use hashbrown::HashMap;
use std::fmt::Display;
use std::num::NonZeroUsize;
use std::ops::DerefMut;
use std::path::{Path, PathBuf};
use std::string::ToString;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use lru::LruCache;
use time::OffsetDateTime;
use tokio::sync::Mutex;
use uuid::Uuid;
use crate::db::{Cache, Db};
use crate::idprovider::interface::{
AuthCredHandler,
AuthResult,
GroupToken,
GroupTokenState,
Id,
IdProvider,
IdpError,
ProviderOrigin,
UserToken,
UserTokenState,
};
use crate::idprovider::system::{
Shadow, SystemAuthResult, SystemProvider, SystemProviderAuthInit, SystemProviderSession,
};
use crate::unix_config::{HomeAttr, UidAttr};
use kanidm_unix_common::unix_passwd::{EtcGroup, EtcShadow, EtcUser};
use kanidm_unix_common::unix_proto::{
HomeDirectoryInfo, NssGroup, NssUser, PamAuthRequest, PamAuthResponse, PamServiceInfo,
ProviderStatus,
};
use kanidm_hsm_crypto::BoxedDynTpm;
use tokio::sync::broadcast;
const NXCACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(128) };
pub enum AuthSession {
Online {
client: Arc<dyn IdProvider + Sync + Send>,
account_id: String,
id: Id,
token: Option<Box<UserToken>>,
cred_handler: AuthCredHandler,
shutdown_rx: broadcast::Receiver<()>,
},
Offline {
account_id: String,
id: Id,
client: Arc<dyn IdProvider + Sync + Send>,
token: Box<UserToken>,
cred_handler: AuthCredHandler,
},
System {
account_id: String,
id: Id,
cred_handler: AuthCredHandler,
shadow: Arc<Shadow>,
},
Success,
Denied,
}
pub struct Resolver {
db: Db,
hsm: Mutex<BoxedDynTpm>,
system_provider: Arc<SystemProvider>,
client_ids: HashMap<ProviderOrigin, Arc<dyn IdProvider + Sync + Send>>,
clients: Vec<Arc<dyn IdProvider + Sync + Send>>,
primary_origin: ProviderOrigin,
timeout_seconds: u64,
default_shell: String,
home_prefix: PathBuf,
home_attr: HomeAttr,
home_alias: Option<HomeAttr>,
uid_attr_map: UidAttr,
gid_attr_map: UidAttr,
nxcache: Mutex<LruCache<Id, SystemTime>>,
}
impl Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&match self {
Id::Name(s) => s.to_string(),
Id::Gid(g) => g.to_string(),
})
}
}
impl Resolver {
#[allow(clippy::too_many_arguments)]
pub async fn new(
db: Db,
system_provider: Arc<SystemProvider>,
clients: Vec<Arc<dyn IdProvider + Sync + Send>>,
hsm: BoxedDynTpm,
timeout_seconds: u64,
default_shell: String,
home_prefix: PathBuf,
home_attr: HomeAttr,
home_alias: Option<HomeAttr>,
uid_attr_map: UidAttr,
gid_attr_map: UidAttr,
) -> Result<Self, ()> {
let hsm = Mutex::new(hsm);
let primary_origin = clients.first().map(|c| c.origin()).unwrap_or_default();
let client_ids: HashMap<_, _> = clients
.iter()
.map(|provider| (provider.origin(), provider.clone()))
.collect();
Ok(Resolver {
db,
hsm,
system_provider,
clients,
primary_origin,
client_ids,
timeout_seconds,
default_shell,
home_prefix,
home_attr,
home_alias,
uid_attr_map,
gid_attr_map,
nxcache: Mutex::new(LruCache::new(NXCACHE_SIZE)),
})
}
#[instrument(level = "debug", skip_all)]
pub async fn mark_next_check_now(&self, now: SystemTime) {
for c in self.clients.iter() {
c.mark_next_check(now).await;
}
}
#[instrument(level = "debug", skip_all)]
pub async fn mark_offline(&self) {
for c in self.clients.iter() {
c.mark_offline().await;
}
}
#[instrument(level = "debug", skip_all)]
pub async fn clear_cache(&self) -> Result<(), ()> {
let mut dbtxn = self.db.write().await;
let mut nxcache_txn = self.nxcache.lock().await;
nxcache_txn.clear();
dbtxn.clear().and_then(|_| dbtxn.commit()).map_err(|_| ())
}
#[instrument(level = "debug", skip_all)]
pub async fn invalidate(&self) -> Result<(), ()> {
let mut dbtxn = self.db.write().await;
let mut nxcache_txn = self.nxcache.lock().await;
nxcache_txn.clear();
dbtxn
.invalidate()
.and_then(|_| dbtxn.commit())
.map_err(|_| ())
}
async fn get_cached_usertokens(&self) -> Result<Vec<UserToken>, ()> {
let mut dbtxn = self.db.write().await;
dbtxn.get_accounts().map_err(|_| ())
}
async fn get_cached_grouptokens(&self) -> Result<Vec<GroupToken>, ()> {
let mut dbtxn = self.db.write().await;
dbtxn.get_groups().map_err(|_| ())
}
async fn set_nxcache(&self, id: &Id) {
let mut nxcache_txn = self.nxcache.lock().await;
let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
nxcache_txn.put(id.clone(), ex_time);
}
pub async fn check_nxcache(&self, id: &Id) -> Option<SystemTime> {
let mut nxcache_txn = self.nxcache.lock().await;
nxcache_txn.get(id).copied()
}
pub async fn reload_system_identities(
&self,
users: Vec<EtcUser>,
shadow: Option<Vec<EtcShadow>>,
groups: Vec<EtcGroup>,
) {
self.system_provider.reload(users, shadow, groups).await
}
async fn get_cached_usertoken(&self, account_id: &Id) -> Result<(bool, Option<UserToken>), ()> {
let mut dbtxn = self.db.write().await;
let r = dbtxn.get_account(account_id).map_err(|err| {
debug!("get_cached_usertoken {:?}", err);
})?;
match r {
Some((ut, ex)) => {
let offset = Duration::from_secs(ex);
let ex_time = SystemTime::UNIX_EPOCH + offset;
let now = SystemTime::now();
if now >= ex_time {
Ok((true, Some(ut)))
} else {
Ok((false, Some(ut)))
}
}
None => {
match self.check_nxcache(account_id).await {
Some(ex_time) => {
let now = SystemTime::now();
if now >= ex_time {
Ok((true, None))
} else {
Ok((false, None))
}
}
None => {
Ok((true, None))
}
}
}
} }
async fn get_cached_grouptoken(&self, grp_id: &Id) -> Result<(bool, Option<GroupToken>), ()> {
let mut dbtxn = self.db.write().await;
let r = dbtxn.get_group(grp_id).map_err(|_| ())?;
match r {
Some((ut, ex)) => {
let offset = Duration::from_secs(ex);
let ex_time = SystemTime::UNIX_EPOCH + offset;
let now = SystemTime::now();
if now >= ex_time {
Ok((true, Some(ut)))
} else {
Ok((false, Some(ut)))
}
}
None => {
match self.check_nxcache(grp_id).await {
Some(ex_time) => {
let now = SystemTime::now();
if now >= ex_time {
Ok((true, None))
} else {
Ok((false, None))
}
}
None => {
Ok((true, None))
}
}
}
}
}
async fn set_cache_usertoken(&self, token: &mut UserToken) -> Result<(), ()> {
let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
let offset = ex_time
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|e| {
error!(
"Time conversion error - cache expiry time became less than epoch? {:?}",
e
);
})?;
let requested_shell_exists: bool = token
.shell
.as_ref()
.map(|shell| {
let exists = Path::new(shell).canonicalize()
.map_err(|err|{
debug!("Failed to canonicalize path, using base path. Tried: {} Error: {:?}", shell, err);
}).unwrap_or(Path::new(shell).to_path_buf()).exists();
if !exists {
warn!(
"Configured shell for {} is not present on this system - {}. Check `/etc/shells` for valid shell options.", token.name,
shell
)
}
exists
})
.unwrap_or_else(|| {
info!("User has not specified a shell, using default");
false
});
if !requested_shell_exists {
token.shell = Some(self.default_shell.clone())
}
let mut dbtxn = self.db.write().await;
token
.groups
.iter()
.try_for_each(|g| dbtxn.update_group(g, offset.as_secs()))
.and_then(|_|
dbtxn
.update_account(token, offset.as_secs()))
.and_then(|_| dbtxn.commit())
.map_err(|_| ())
}
async fn set_cache_grouptoken(&self, token: &GroupToken) -> Result<(), ()> {
let ex_time = SystemTime::now() + Duration::from_secs(self.timeout_seconds);
let offset = ex_time
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(|e| {
error!("time conversion error - ex_time less than epoch? {:?}", e);
})?;
let mut dbtxn = self.db.write().await;
dbtxn
.update_group(token, offset.as_secs())
.and_then(|_| dbtxn.commit())
.map_err(|_| ())
}
async fn delete_cache_usertoken(&self, a_uuid: Uuid) -> Result<(), ()> {
let mut dbtxn = self.db.write().await;
dbtxn
.delete_account(a_uuid)
.and_then(|_| dbtxn.commit())
.map_err(|_| ())
}
async fn delete_cache_grouptoken(&self, g_uuid: Uuid) -> Result<(), ()> {
let mut dbtxn = self.db.write().await;
dbtxn
.delete_group(g_uuid)
.and_then(|_| dbtxn.commit())
.map_err(|_| ())
}
async fn refresh_usertoken(
&self,
account_id: &Id,
token: Option<UserToken>,
) -> Result<Option<UserToken>, ()> {
let now = SystemTime::now();
let mut hsm_lock = self.hsm.lock().await;
let user_get_result = if let Some(tok) = token.as_ref() {
match self.client_ids.get(&tok.provider) {
Some(client) => {
client
.unix_user_get(account_id, token.as_ref(), hsm_lock.deref_mut(), now)
.await
}
None => {
error!(provider = ?tok.provider, "Token was resolved by a provider that no longer appears to be present.");
Ok(UserTokenState::NotFound)
}
}
} else {
'search: {
for client in self.clients.iter() {
match client
.unix_user_get(account_id, token.as_ref(), hsm_lock.deref_mut(), now)
.await
{
Ok(UserTokenState::NotFound) => {}
result => break 'search result,
}
}
break 'search Ok(UserTokenState::NotFound);
}
};
drop(hsm_lock);
match user_get_result {
Ok(UserTokenState::Update(mut n_tok)) => {
self.set_cache_usertoken(&mut n_tok).await?;
Ok(Some(n_tok))
}
Ok(UserTokenState::NotFound) => {
if let Some(tok) = token {
self.delete_cache_usertoken(tok.uuid).await?;
};
self.set_nxcache(account_id).await;
Ok(None)
}
Ok(UserTokenState::UseCached) => Ok(token),
Err(err) => {
error!(?err);
Ok(token)
}
}
}
async fn refresh_grouptoken(
&self,
grp_id: &Id,
token: Option<GroupToken>,
) -> Result<Option<GroupToken>, ()> {
let now = SystemTime::now();
let mut hsm_lock = self.hsm.lock().await;
let group_get_result = if let Some(tok) = token.as_ref() {
match self.client_ids.get(&tok.provider) {
Some(client) => {
client
.unix_group_get(grp_id, hsm_lock.deref_mut(), now)
.await
}
None => {
error!(provider = ?tok.provider, "Token was resolved by a provider that no longer appears to be present.");
Ok(GroupTokenState::NotFound)
}
}
} else {
'search: {
for client in self.clients.iter() {
match client
.unix_group_get(grp_id, hsm_lock.deref_mut(), now)
.await
{
Ok(GroupTokenState::NotFound) => {}
result => break 'search result,
}
}
break 'search Ok(GroupTokenState::NotFound);
}
};
drop(hsm_lock);
match group_get_result {
Ok(GroupTokenState::Update(n_tok)) => {
self.set_cache_grouptoken(&n_tok).await?;
Ok(Some(n_tok))
}
Ok(GroupTokenState::NotFound) => {
if let Some(tok) = token {
self.delete_cache_grouptoken(tok.uuid).await?;
};
self.set_nxcache(grp_id).await;
Ok(None)
}
Ok(GroupTokenState::UseCached) => Ok(token),
Err(err) => {
error!(?err);
Ok(token)
}
}
}
#[instrument(level = "debug", skip(self))]
async fn get_usertoken(&self, account_id: &Id) -> Result<Option<UserToken>, ()> {
let (expired, item) = self.get_cached_usertoken(account_id).await.map_err(|e| {
debug!("get_usertoken error -> {:?}", e);
})?;
if expired {
self.refresh_usertoken(account_id, item).await
} else {
Ok(item)
}
.map(|t| {
debug!("token -> {:?}", t);
t
})
}
#[instrument(level = "debug", skip(self))]
async fn get_grouptoken(&self, grp_id: Id) -> Result<Option<GroupToken>, ()> {
let (expired, item) = self.get_cached_grouptoken(&grp_id).await.map_err(|e| {
debug!("get_grouptoken error -> {:?}", e);
})?;
if expired {
self.refresh_grouptoken(&grp_id, item).await
} else {
Ok(item)
}
.map(|t| {
debug!("token -> {:?}", t);
t
})
}
async fn get_groupmembers(&self, g_uuid: Uuid) -> Vec<String> {
let mut dbtxn = self.db.write().await;
dbtxn
.get_group_members(g_uuid)
.unwrap_or_else(|_| Vec::new())
.into_iter()
.map(|ut| self.token_uidattr(&ut))
.collect()
}
#[instrument(level = "debug", skip(self))]
pub async fn get_sshkeys(&self, account_id: &str) -> Result<Vec<String>, ()> {
let token = self
.get_usertoken(&Id::Name(account_id.to_string()))
.await?;
Ok(token
.map(|t| {
if t.valid {
t.sshkeys
} else {
Vec::with_capacity(0)
}
})
.unwrap_or_else(|| Vec::with_capacity(0)))
}
fn token_homedirectory_alias(&self, token: &UserToken) -> Option<String> {
let is_primary_origin = token.provider == self.primary_origin;
self.home_alias.map(|t| match t {
HomeAttr::Name if is_primary_origin => token.name.as_str().to_string(),
HomeAttr::Uuid => token.uuid.hyphenated().to_string(),
HomeAttr::Spn | HomeAttr::Name => token.spn.as_str().to_string(),
})
}
fn token_homedirectory_attr(&self, token: &UserToken) -> String {
let is_primary_origin = token.provider == self.primary_origin;
match self.home_attr {
HomeAttr::Name if is_primary_origin => token.name.as_str().to_string(),
HomeAttr::Uuid => token.uuid.hyphenated().to_string(),
HomeAttr::Spn | HomeAttr::Name => token.spn.as_str().to_string(),
}
}
fn token_homedirectory(&self, token: &UserToken) -> String {
self.token_homedirectory_alias(token)
.unwrap_or_else(|| self.token_homedirectory_attr(token))
}
fn token_abs_homedirectory(&self, token: &UserToken) -> String {
self.home_prefix
.join(self.token_homedirectory(token))
.to_string_lossy()
.to_string()
}
fn token_uidattr(&self, token: &UserToken) -> String {
let is_primary_origin = token.provider == self.primary_origin;
match self.uid_attr_map {
UidAttr::Name if is_primary_origin => token.name.as_str(),
UidAttr::Spn | UidAttr::Name => token.spn.as_str(),
}
.to_string()
}
#[instrument(level = "debug", skip_all)]
pub async fn get_nssaccounts(&self) -> Result<Vec<NssUser>, ()> {
let system_nss_users = self.system_provider.get_nssaccounts().await;
let cached = self.get_cached_usertokens().await?;
Ok(system_nss_users
.into_iter()
.chain(cached.into_iter().map(|tok| NssUser {
homedir: self.token_abs_homedirectory(&tok),
name: self.token_uidattr(&tok),
uid: tok.gidnumber,
gid: tok.gidnumber,
gecos: tok.displayname,
shell: tok.shell.unwrap_or_else(|| self.default_shell.clone()),
}))
.collect())
}
#[instrument(level = "debug", skip_all)]
async fn get_nssaccount(&self, account_id: Id) -> Result<Option<NssUser>, ()> {
if let Some(nss_user) = self.system_provider.get_nssaccount(&account_id).await {
debug!("system provider satisfied request");
return Ok(Some(nss_user));
}
let token = self.get_usertoken(&account_id).await?;
Ok(token.map(|tok| NssUser {
homedir: self.token_abs_homedirectory(&tok),
name: self.token_uidattr(&tok),
uid: tok.gidnumber,
gid: tok.gidnumber,
gecos: tok.displayname,
shell: tok.shell.unwrap_or_else(|| self.default_shell.clone()),
}))
}
#[instrument(level = "debug", skip(self))]
pub async fn get_nssaccount_name(&self, account_id: &str) -> Result<Option<NssUser>, ()> {
self.get_nssaccount(Id::Name(account_id.to_string())).await
}
#[instrument(level = "debug", skip(self))]
pub async fn get_nssaccount_gid(&self, gid: u32) -> Result<Option<NssUser>, ()> {
self.get_nssaccount(Id::Gid(gid)).await
}
fn token_gidattr(&self, token: &GroupToken) -> String {
match self.gid_attr_map {
UidAttr::Spn => token.spn.as_str(),
UidAttr::Name => token.name.as_str(),
}
.to_string()
}
#[instrument(level = "debug", skip_all)]
pub async fn get_nssgroups(&self) -> Result<Vec<NssGroup>, ()> {
let mut r = self.system_provider.get_nssgroups().await;
for nss_group in r.iter_mut() {
for client in self.clients.iter() {
if let Some(extend_group_id) = client.has_map_group(&nss_group.name) {
let (_, token) = self.get_cached_grouptoken(extend_group_id).await?;
if let Some(token) = token {
let members = self.get_groupmembers(token.uuid).await;
nss_group.members.extend(members);
debug!(
"extended group {} with members from {}",
nss_group.name, token.name
);
}
}
}
}
let l = self.get_cached_grouptokens().await?;
r.reserve(l.len());
for tok in l.into_iter() {
let members = self.get_groupmembers(tok.uuid).await;
r.push(NssGroup {
name: self.token_gidattr(&tok),
gid: tok.gidnumber,
members,
})
}
Ok(r)
}
async fn get_nssgroup(&self, grp_id: Id) -> Result<Option<NssGroup>, ()> {
if let Some(mut nss_group) = self.system_provider.get_nssgroup(&grp_id).await {
debug!("system provider satisfied request");
for client in self.clients.iter() {
if let Some(extend_group_id) = client.has_map_group(&nss_group.name) {
let token = self.get_grouptoken(extend_group_id.clone()).await?;
if let Some(token) = token {
let members = self.get_groupmembers(token.uuid).await;
nss_group.members.extend(members);
debug!(
"extended group {} with members from {}",
nss_group.name, token.name
);
}
}
}
nss_group.members.sort_unstable();
nss_group.members.dedup();
return Ok(Some(nss_group));
}
let token = self.get_grouptoken(grp_id).await?;
match token {
Some(tok) => {
let members = self.get_groupmembers(tok.uuid).await;
Ok(Some(NssGroup {
name: self.token_gidattr(&tok),
gid: tok.gidnumber,
members,
}))
}
None => Ok(None),
}
}
#[instrument(level = "debug", skip(self))]
pub async fn get_nssgroup_name(&self, grp_id: &str) -> Result<Option<NssGroup>, ()> {
self.get_nssgroup(Id::Name(grp_id.to_string())).await
}
#[instrument(level = "debug", skip(self))]
pub async fn get_nssgroup_gid(&self, gid: u32) -> Result<Option<NssGroup>, ()> {
self.get_nssgroup(Id::Gid(gid)).await
}
#[instrument(level = "debug", skip(self))]
pub async fn pam_account_allowed(&self, account_id: &str) -> Result<Option<bool>, ()> {
let id = Id::Name(account_id.to_string());
if let Some(answer) = self.system_provider.authorise(&id).await {
return Ok(Some(answer));
};
let token = self.get_usertoken(&id).await?;
match token {
Some(token) => {
let client = self.client_ids.get(&token.provider)
.cloned()
.ok_or_else(|| {
error!(provider = ?token.provider, "Token was resolved by a provider that no longer appears to be present.");
})?;
client.unix_user_authorise(&token).await.map_err(|err| {
error!(?err, "unable to authorise account");
})
}
None => Ok(None),
}
}
#[instrument(level = "debug", skip(self, shutdown_rx))]
pub async fn pam_account_authenticate_init(
&self,
account_id: &str,
pam_info: &PamServiceInfo,
current_time: OffsetDateTime,
shutdown_rx: broadcast::Receiver<()>,
) -> Result<(AuthSession, PamAuthResponse), ()> {
let now = SystemTime::now();
let id = Id::Name(account_id.to_string());
match self.system_provider.auth_init(&id, current_time).await {
SystemProviderAuthInit::Ignore => {
debug!(?account_id, "account unknown to system provider, continue.");
}
SystemProviderAuthInit::ShadowMissing => {
warn!(
?account_id,
"Resolver unable to proceed, /etc/shadow was not accessible."
);
return Ok((AuthSession::Denied, PamAuthResponse::Unknown));
}
SystemProviderAuthInit::CredentialsUnavailable => {
warn!(
?account_id,
"Denying auth request for system user with no valid credentials"
);
return Ok((AuthSession::Denied, PamAuthResponse::Denied));
}
SystemProviderAuthInit::Expired => {
warn!(
?account_id,
"Denying auth request for system user with expired credentials"
);
return Ok((AuthSession::Denied, PamAuthResponse::Denied));
}
SystemProviderAuthInit::Begin {
next_request,
cred_handler,
shadow,
} => {
let auth_session = AuthSession::System {
account_id: account_id.to_string(),
id,
shadow,
cred_handler,
};
return Ok((auth_session, next_request.into()));
}
}
let token = self.get_usertoken(&id).await?;
let mut hsm_lock = self.hsm.lock().await;
if let Some(token) = token {
let client = self.client_ids.get(&token.provider)
.cloned()
.ok_or_else(|| {
error!(provider = ?token.provider, "Token was resolved by a provider that no longer appears to be present.");
})?;
let online_at_init = client.attempt_online(hsm_lock.deref_mut(), now).await;
debug!(?online_at_init);
if online_at_init {
let init_result = client
.unix_user_online_auth_init(
account_id,
&token,
hsm_lock.deref_mut(),
&shutdown_rx,
)
.await;
match init_result {
Ok((next_req, cred_handler)) => {
let auth_session = AuthSession::Online {
client,
account_id: account_id.to_string(),
id,
token: Some(Box::new(token)),
cred_handler,
shutdown_rx,
};
Ok((auth_session, next_req.into()))
}
Err(err) => {
error!(?err, "Unable to start authentication");
Err(())
}
}
} else {
let init_result = client.unix_user_offline_auth_init(&token).await;
match init_result {
Ok((next_req, cred_handler)) => {
let auth_session = AuthSession::Offline {
account_id: account_id.to_string(),
id,
client,
token: Box::new(token),
cred_handler,
};
Ok((auth_session, next_req.into()))
}
Err(err) => {
error!(?err, "Unable to start authentication");
Err(())
}
}
}
} else {
for client in self.clients.iter() {
let online_at_init = client.attempt_online(hsm_lock.deref_mut(), now).await;
debug!(?online_at_init);
if !online_at_init {
warn!(?account_id, "Unable to proceed with authentication, all providers must be online for unknown user authentication.");
return Ok((AuthSession::Denied, PamAuthResponse::Unknown));
}
}
for client in self.clients.iter() {
let init_result = client
.unix_unknown_user_online_auth_init(
account_id,
hsm_lock.deref_mut(),
&shutdown_rx,
)
.await;
match init_result {
Ok(Some((next_req, cred_handler))) => {
let auth_session = AuthSession::Online {
client: client.clone(),
account_id: account_id.to_string(),
id,
token: None,
cred_handler,
shutdown_rx,
};
return Ok((auth_session, next_req.into()));
}
Ok(None) => {
}
Err(err) => {
error!(?err, "Unable to start authentication");
return Err(());
}
}
}
warn!("No provider is willing to service authentication of unknown account.");
Ok((AuthSession::Denied, PamAuthResponse::Unknown))
}
}
#[instrument(level = "debug", skip_all)]
pub async fn pam_account_authenticate_step(
&self,
auth_session: &mut AuthSession,
pam_next_req: PamAuthRequest,
) -> Result<PamAuthResponse, ()> {
let maybe_err = match &mut *auth_session {
&mut AuthSession::Online {
ref client,
ref account_id,
id: _,
token: _,
ref mut cred_handler,
ref shutdown_rx,
} => {
let mut hsm_lock = self.hsm.lock().await;
let result = client
.unix_user_online_auth_step(
account_id,
cred_handler,
pam_next_req,
hsm_lock.deref_mut(),
shutdown_rx,
)
.await;
match result {
Ok(AuthResult::Success { .. }) => {
info!(?account_id, "Authentication Success");
}
Ok(AuthResult::Denied) => {
info!(?account_id, "Authentication Denied");
}
Ok(AuthResult::Next(_)) => {
info!(?account_id, "Authentication Continue");
}
_ => {}
};
result
}
&mut AuthSession::Offline {
ref account_id,
id: _,
ref client,
ref token,
ref mut cred_handler,
} => {
let mut hsm_lock = self.hsm.lock().await;
let result = client
.unix_user_offline_auth_step(
token,
cred_handler,
pam_next_req,
hsm_lock.deref_mut(),
)
.await;
match result {
Ok(AuthResult::Success { .. }) => {
info!(?account_id, "Authentication Success");
}
Ok(AuthResult::Denied) => {
info!(?account_id, "Authentication Denied");
}
Ok(AuthResult::Next(_)) => {
info!(?account_id, "Authentication Continue");
}
_ => {}
};
result
}
&mut AuthSession::System {
ref account_id,
id: _,
ref mut cred_handler,
ref shadow,
} => {
let system_auth_result = shadow.auth_step(cred_handler, pam_next_req);
let next = match system_auth_result {
SystemAuthResult::Denied => {
info!(?account_id, "Authentication Denied");
*auth_session = AuthSession::Denied;
Ok(PamAuthResponse::Denied)
}
SystemAuthResult::Success => {
info!(?account_id, "Authentication Success");
*auth_session = AuthSession::Success;
Ok(PamAuthResponse::Success)
}
SystemAuthResult::Next(req) => Ok(req.into()),
};
return next;
}
&mut AuthSession::Success | &mut AuthSession::Denied => Err(IdpError::BadRequest),
};
match maybe_err {
Ok(AuthResult::Success { mut token }) => {
self.set_cache_usertoken(&mut token).await?;
*auth_session = AuthSession::Success;
Ok(PamAuthResponse::Success)
}
Ok(AuthResult::Denied) => {
*auth_session = AuthSession::Denied;
Ok(PamAuthResponse::Denied)
}
Ok(AuthResult::Next(req)) => Ok(req.into()),
Err(IdpError::NotFound) => {
*auth_session = AuthSession::Denied;
Ok(PamAuthResponse::Unknown)
}
Err(err) => {
*auth_session = AuthSession::Denied;
error!(?err, "Unable to proceed, failing the session");
Err(())
}
}
}
#[instrument(level = "debug", skip(self, password))]
pub async fn pam_account_authenticate(
&self,
account_id: &str,
current_time: OffsetDateTime,
password: &str,
) -> Result<Option<bool>, ()> {
let (_shutdown_tx, shutdown_rx) = broadcast::channel(1);
let pam_info = PamServiceInfo {
service: "kanidm-unix-test".to_string(),
tty: Some("/dev/null".to_string()),
rhost: None,
};
let mut auth_session = match self
.pam_account_authenticate_init(account_id, &pam_info, current_time, shutdown_rx)
.await?
{
(auth_session, PamAuthResponse::Password) => {
auth_session
}
(auth_session, PamAuthResponse::DeviceAuthorizationGrant { .. }) => {
auth_session
}
(auth_session, PamAuthResponse::MFACode { .. }) => {
auth_session
}
(auth_session, PamAuthResponse::MFAPoll { .. }) => {
auth_session
}
(auth_session, PamAuthResponse::MFAPollWait) => {
auth_session
}
(auth_session, PamAuthResponse::SetupPin { .. }) => {
auth_session
}
(auth_session, PamAuthResponse::Pin) => {
auth_session
}
(_, PamAuthResponse::Unknown) => return Ok(None),
(_, PamAuthResponse::Denied) => return Ok(Some(false)),
(_, PamAuthResponse::Success) => {
debug_assert!(false);
return Ok(Some(true));
}
};
let pam_next_req = PamAuthRequest::Password {
cred: password.to_string(),
};
match self
.pam_account_authenticate_step(&mut auth_session, pam_next_req)
.await?
{
PamAuthResponse::Success => Ok(Some(true)),
PamAuthResponse::Denied => Ok(Some(false)),
_ => {
Ok(None)
}
}
}
#[instrument(level = "debug", skip(self))]
pub async fn pam_account_beginsession(
&self,
account_id: &str,
) -> Result<Option<HomeDirectoryInfo>, ()> {
let id = Id::Name(account_id.to_string());
match self.system_provider.begin_session(&id).await {
SystemProviderSession::Start => {
return Ok(None);
}
SystemProviderSession::Ignore => {}
};
let token = self.get_usertoken(&id).await?;
Ok(token.as_ref().map(|tok| HomeDirectoryInfo {
uid: tok.gidnumber,
gid: tok.gidnumber,
name: self.token_homedirectory_attr(tok),
aliases: self
.token_homedirectory_alias(tok)
.map(|s| vec![s])
.unwrap_or_default(),
}))
}
pub async fn provider_status(&self) -> Vec<ProviderStatus> {
let now = SystemTime::now();
let mut hsm_lock = self.hsm.lock().await;
let mut results = Vec::with_capacity(self.clients.len() + 1);
results.push(ProviderStatus {
name: "system".to_string(),
online: true,
});
for client in self.clients.iter() {
let online = client.attempt_online(hsm_lock.deref_mut(), now).await;
let name = client.origin().to_string();
results.push(ProviderStatus { name, online })
}
results
}
#[instrument(level = "debug", skip_all)]
pub async fn test_connection(&self) -> bool {
let now = SystemTime::now();
let mut hsm_lock = self.hsm.lock().await;
for client in self.clients.iter() {
let status = client.attempt_online(hsm_lock.deref_mut(), now).await;
if !status {
return false;
}
}
true
}
}