mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Authentication shortcut to get a RW session (#1993)
* auth: Add a privileged flag to AuthStep::Init2 step to request a rw session The privileged flag is defined as Option<bool> for compatibility with existing clients.
This commit is contained in:
parent
47e953bfd2
commit
9dda8b1ad3
|
@ -955,6 +955,7 @@ impl KanidmClient {
|
||||||
step: AuthStep::Init2 {
|
step: AuthStep::Init2 {
|
||||||
username: ident.to_string(),
|
username: ident.to_string(),
|
||||||
issue: AuthIssueSession::Token,
|
issue: AuthIssueSession::Token,
|
||||||
|
privileged: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -919,6 +919,8 @@ pub enum AuthStep {
|
||||||
Init2 {
|
Init2 {
|
||||||
username: String,
|
username: String,
|
||||||
issue: AuthIssueSession,
|
issue: AuthIssueSession,
|
||||||
|
#[serde(default)]
|
||||||
|
privileged: bool,
|
||||||
},
|
},
|
||||||
// We want to talk to you like this.
|
// We want to talk to you like this.
|
||||||
Begin(AuthMech),
|
Begin(AuthMech),
|
||||||
|
|
|
@ -77,7 +77,9 @@ impl fmt::Display for AuthType {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum AuthIntent {
|
enum AuthIntent {
|
||||||
InitialAuth,
|
InitialAuth {
|
||||||
|
privileged: bool,
|
||||||
|
},
|
||||||
Reauth {
|
Reauth {
|
||||||
session_id: Uuid,
|
session_id: Uuid,
|
||||||
session_expiry: Option<OffsetDateTime>,
|
session_expiry: Option<OffsetDateTime>,
|
||||||
|
@ -751,6 +753,7 @@ impl AuthSession {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
account: Account,
|
account: Account,
|
||||||
issue: AuthIssueSession,
|
issue: AuthIssueSession,
|
||||||
|
privileged: bool,
|
||||||
webauthn: &Webauthn,
|
webauthn: &Webauthn,
|
||||||
ct: Duration,
|
ct: Duration,
|
||||||
source: Source,
|
source: Source,
|
||||||
|
@ -808,7 +811,7 @@ impl AuthSession {
|
||||||
account,
|
account,
|
||||||
state,
|
state,
|
||||||
issue,
|
issue,
|
||||||
intent: AuthIntent::InitialAuth,
|
intent: AuthIntent::InitialAuth { privileged },
|
||||||
source,
|
source,
|
||||||
};
|
};
|
||||||
// Get the set of mechanisms that can proceed. This is tied
|
// Get the set of mechanisms that can proceed. This is tied
|
||||||
|
@ -1109,7 +1112,7 @@ impl AuthSession {
|
||||||
) -> Result<UserAuthToken, OperationError> {
|
) -> Result<UserAuthToken, OperationError> {
|
||||||
security_debug!("Successful cred handling");
|
security_debug!("Successful cred handling");
|
||||||
match self.intent {
|
match self.intent {
|
||||||
AuthIntent::InitialAuth => {
|
AuthIntent::InitialAuth { privileged } => {
|
||||||
let session_id = Uuid::new_v4();
|
let session_id = Uuid::new_v4();
|
||||||
// We need to actually work this out better, and then
|
// We need to actually work this out better, and then
|
||||||
// pass it to to_userauthtoken
|
// pass it to to_userauthtoken
|
||||||
|
@ -1117,13 +1120,18 @@ impl AuthSession {
|
||||||
AuthType::UnixPassword | AuthType::Anonymous => SessionScope::ReadOnly,
|
AuthType::UnixPassword | AuthType::Anonymous => SessionScope::ReadOnly,
|
||||||
AuthType::GeneratedPassword => SessionScope::ReadWrite,
|
AuthType::GeneratedPassword => SessionScope::ReadWrite,
|
||||||
AuthType::Password | AuthType::PasswordMfa | AuthType::Passkey => {
|
AuthType::Password | AuthType::PasswordMfa | AuthType::Passkey => {
|
||||||
SessionScope::PrivilegeCapable
|
if privileged {
|
||||||
|
SessionScope::ReadWrite
|
||||||
|
} else {
|
||||||
|
SessionScope::PrivilegeCapable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
security_info!(
|
security_info!(
|
||||||
"Issuing {:?} session {} for {} {}",
|
"Issuing {:?} session ({:?}) {} for {} {}",
|
||||||
self.issue,
|
self.issue,
|
||||||
|
scope,
|
||||||
session_id,
|
session_id,
|
||||||
self.account.spn,
|
self.account.spn,
|
||||||
self.account.uuid
|
self.account.uuid
|
||||||
|
@ -1229,11 +1237,14 @@ impl AuthSession {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
pub use std::collections::BTreeSet as Set;
|
pub use std::collections::BTreeSet as Set;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use compact_jwt::JwsSigner;
|
use compact_jwt::{Jws, JwsSigner, JwsUnverified};
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthIssueSession, AuthMech};
|
use kanidm_proto::v1::{
|
||||||
|
AuthAllowed, AuthCredential, AuthIssueSession, AuthMech, UatPurpose, UserAuthToken,
|
||||||
|
};
|
||||||
use tokio::sync::mpsc::unbounded_channel as unbounded;
|
use tokio::sync::mpsc::unbounded_channel as unbounded;
|
||||||
use webauthn_authenticator_rs::softpasskey::SoftPasskey;
|
use webauthn_authenticator_rs::softpasskey::SoftPasskey;
|
||||||
use webauthn_authenticator_rs::WebauthnAuthenticator;
|
use webauthn_authenticator_rs::WebauthnAuthenticator;
|
||||||
|
@ -1282,6 +1293,7 @@ mod tests {
|
||||||
let (session, state) = AuthSession::new(
|
let (session, state) = AuthSession::new(
|
||||||
anon_account,
|
anon_account,
|
||||||
AuthIssueSession::Token,
|
AuthIssueSession::Token,
|
||||||
|
false,
|
||||||
&webauthn,
|
&webauthn,
|
||||||
duration_from_epoch_now(),
|
duration_from_epoch_now(),
|
||||||
Source::Internal,
|
Source::Internal,
|
||||||
|
@ -1311,11 +1323,13 @@ mod tests {
|
||||||
(
|
(
|
||||||
$audit:expr,
|
$audit:expr,
|
||||||
$account:expr,
|
$account:expr,
|
||||||
$webauthn:expr
|
$webauthn:expr,
|
||||||
|
$privileged:expr
|
||||||
) => {{
|
) => {{
|
||||||
let (session, state) = AuthSession::new(
|
let (session, state) = AuthSession::new(
|
||||||
$account.clone(),
|
$account.clone(),
|
||||||
AuthIssueSession::Token,
|
AuthIssueSession::Token,
|
||||||
|
$privileged,
|
||||||
$webauthn,
|
$webauthn,
|
||||||
duration_from_epoch_now(),
|
duration_from_epoch_now(),
|
||||||
Source::Internal,
|
Source::Internal,
|
||||||
|
@ -1344,9 +1358,7 @@ mod tests {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn start_session_simple_password_mech(privileged: bool) -> UserAuthToken {
|
||||||
fn test_idm_authsession_simple_password_mech() {
|
|
||||||
sketching::test_init();
|
|
||||||
let webauthn = create_webauthn();
|
let webauthn = create_webauthn();
|
||||||
// create the ent
|
// create the ent
|
||||||
let mut account = entry_to_account!(E_ADMIN_V1.clone());
|
let mut account = entry_to_account!(E_ADMIN_V1.clone());
|
||||||
|
@ -1360,7 +1372,7 @@ mod tests {
|
||||||
|
|
||||||
// now check
|
// now check
|
||||||
let (mut session, pw_badlist_cache) =
|
let (mut session, pw_badlist_cache) =
|
||||||
start_password_session!(&mut audit, account, &webauthn);
|
start_password_session!(&mut audit, account, &webauthn, false);
|
||||||
|
|
||||||
let attempt = AuthCredential::Password("bad_password".to_string());
|
let attempt = AuthCredential::Password("bad_password".to_string());
|
||||||
let jws_signer = create_jwt_signer();
|
let jws_signer = create_jwt_signer();
|
||||||
|
@ -1385,10 +1397,10 @@ mod tests {
|
||||||
// === Now begin a new session, and use a good pw.
|
// === Now begin a new session, and use a good pw.
|
||||||
|
|
||||||
let (mut session, pw_badlist_cache) =
|
let (mut session, pw_badlist_cache) =
|
||||||
start_password_session!(&mut audit, account, &webauthn);
|
start_password_session!(&mut audit, account, &webauthn, privileged);
|
||||||
|
|
||||||
let attempt = AuthCredential::Password("test_password".to_string());
|
let attempt = AuthCredential::Password("test_password".to_string());
|
||||||
match session.validate_creds(
|
let uat: UserAuthToken = match session.validate_creds(
|
||||||
&attempt,
|
&attempt,
|
||||||
Duration::from_secs(0),
|
Duration::from_secs(0),
|
||||||
&async_tx,
|
&async_tx,
|
||||||
|
@ -1397,7 +1409,12 @@ mod tests {
|
||||||
&jws_signer,
|
&jws_signer,
|
||||||
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
|
&AccountPolicy::from_pw_badlist_cache(pw_badlist_cache),
|
||||||
) {
|
) {
|
||||||
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
|
Ok(AuthState::Success(jwt, AuthIssueSession::Token)) => {
|
||||||
|
let uat = JwsUnverified::from_str(&jwt).expect("Failed to parse jwt");
|
||||||
|
let uat: Jws<UserAuthToken> =
|
||||||
|
uat.validate_embeded().expect("Embedded uat not found");
|
||||||
|
uat.into_inner()
|
||||||
|
}
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1410,6 +1427,34 @@ mod tests {
|
||||||
assert!(async_rx.blocking_recv().is_none());
|
assert!(async_rx.blocking_recv().is_none());
|
||||||
drop(audit_tx);
|
drop(audit_tx);
|
||||||
assert!(audit_rx.blocking_recv().is_none());
|
assert!(audit_rx.blocking_recv().is_none());
|
||||||
|
|
||||||
|
uat
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_authsession_simple_password_mech() {
|
||||||
|
sketching::test_init();
|
||||||
|
let uat = start_session_simple_password_mech(false);
|
||||||
|
match uat.purpose {
|
||||||
|
UatPurpose::ReadOnly => panic!("Unexpected UatPurpose::ReadOnly"),
|
||||||
|
UatPurpose::ReadWrite { expiry } => {
|
||||||
|
// Long lived RO session capable of reauth
|
||||||
|
assert!(expiry.is_none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_authsession_simple_password_mech_priv_shortcut() {
|
||||||
|
sketching::test_init();
|
||||||
|
let uat = start_session_simple_password_mech(true);
|
||||||
|
match uat.purpose {
|
||||||
|
UatPurpose::ReadOnly => panic!("Unexpected UatPurpose::ReadOnly"),
|
||||||
|
UatPurpose::ReadWrite { expiry } => {
|
||||||
|
// Short lived RW session
|
||||||
|
assert!(expiry.is_some())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1429,7 +1474,7 @@ mod tests {
|
||||||
|
|
||||||
// now check, even though the password is correct, Auth should be denied since it is in badlist
|
// now check, even though the password is correct, Auth should be denied since it is in badlist
|
||||||
let (mut session, pw_badlist_cache) =
|
let (mut session, pw_badlist_cache) =
|
||||||
start_password_session!(&mut audit, account, &webauthn);
|
start_password_session!(&mut audit, account, &webauthn, false);
|
||||||
|
|
||||||
let attempt = AuthCredential::Password("list@no3IBTyqHu$bad".to_string());
|
let attempt = AuthCredential::Password("list@no3IBTyqHu$bad".to_string());
|
||||||
match session.validate_creds(
|
match session.validate_creds(
|
||||||
|
@ -1464,6 +1509,7 @@ mod tests {
|
||||||
let (session, state) = AuthSession::new(
|
let (session, state) = AuthSession::new(
|
||||||
$account.clone(),
|
$account.clone(),
|
||||||
AuthIssueSession::Token,
|
AuthIssueSession::Token,
|
||||||
|
false,
|
||||||
$webauthn,
|
$webauthn,
|
||||||
duration_from_epoch_now(),
|
duration_from_epoch_now(),
|
||||||
Source::Internal,
|
Source::Internal,
|
||||||
|
@ -1790,6 +1836,7 @@ mod tests {
|
||||||
let (session, state) = AuthSession::new(
|
let (session, state) = AuthSession::new(
|
||||||
$account.clone(),
|
$account.clone(),
|
||||||
AuthIssueSession::Token,
|
AuthIssueSession::Token,
|
||||||
|
false,
|
||||||
$webauthn,
|
$webauthn,
|
||||||
duration_from_epoch_now(),
|
duration_from_epoch_now(),
|
||||||
Source::Internal,
|
Source::Internal,
|
||||||
|
|
|
@ -280,6 +280,7 @@ impl LdapTokenAuthEvent {
|
||||||
pub struct AuthEventStepInit {
|
pub struct AuthEventStepInit {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub issue: AuthIssueSession,
|
pub issue: AuthIssueSession,
|
||||||
|
pub privileged: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -311,14 +312,23 @@ impl AuthEventStep {
|
||||||
Ok(AuthEventStep::Init(AuthEventStepInit {
|
Ok(AuthEventStep::Init(AuthEventStepInit {
|
||||||
username,
|
username,
|
||||||
issue: AuthIssueSession::Token,
|
issue: AuthIssueSession::Token,
|
||||||
|
privileged: false,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AuthStep::Init2 { username, issue } => {
|
AuthStep::Init2 {
|
||||||
|
username,
|
||||||
|
issue,
|
||||||
|
privileged,
|
||||||
|
} => {
|
||||||
if username.trim().is_empty() {
|
if username.trim().is_empty() {
|
||||||
Err(OperationError::EmptyRequest)
|
Err(OperationError::EmptyRequest)
|
||||||
} else {
|
} else {
|
||||||
Ok(AuthEventStep::Init(AuthEventStepInit { username, issue }))
|
Ok(AuthEventStep::Init(AuthEventStepInit {
|
||||||
|
username,
|
||||||
|
issue,
|
||||||
|
privileged,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,6 +352,7 @@ impl AuthEventStep {
|
||||||
AuthEventStep::Init(AuthEventStepInit {
|
AuthEventStep::Init(AuthEventStepInit {
|
||||||
username: "anonymous".to_string(),
|
username: "anonymous".to_string(),
|
||||||
issue: AuthIssueSession::Token,
|
issue: AuthIssueSession::Token,
|
||||||
|
privileged: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,6 +361,7 @@ impl AuthEventStep {
|
||||||
AuthEventStep::Init(AuthEventStepInit {
|
AuthEventStep::Init(AuthEventStepInit {
|
||||||
username: name.to_string(),
|
username: name.to_string(),
|
||||||
issue: AuthIssueSession::Token,
|
issue: AuthIssueSession::Token,
|
||||||
|
privileged: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1006,6 +1006,7 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
||||||
security_info!(
|
security_info!(
|
||||||
username = %init.username,
|
username = %init.username,
|
||||||
issue = ?init.issue,
|
issue = ?init.issue,
|
||||||
|
privileged = ?init.privileged,
|
||||||
uuid = %euuid,
|
uuid = %euuid,
|
||||||
"Initiating Authentication Session",
|
"Initiating Authentication Session",
|
||||||
);
|
);
|
||||||
|
@ -1048,8 +1049,14 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
||||||
slock_ref
|
slock_ref
|
||||||
});
|
});
|
||||||
|
|
||||||
let (auth_session, state) =
|
let (auth_session, state) = AuthSession::new(
|
||||||
AuthSession::new(account, init.issue, self.webauthn, ct, source);
|
account,
|
||||||
|
init.issue,
|
||||||
|
init.privileged,
|
||||||
|
self.webauthn,
|
||||||
|
ct,
|
||||||
|
source,
|
||||||
|
);
|
||||||
|
|
||||||
match auth_session {
|
match auth_session {
|
||||||
Some(auth_session) => {
|
Some(auth_session) => {
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use kanidm_proto::v1::{
|
use kanidm_proto::v1::{
|
||||||
ApiToken, CURegState, CredentialDetailType, Entry, Filter, Modify, ModifyList, UserAuthToken,
|
ApiToken, AuthCredential, AuthIssueSession, AuthMech, AuthRequest, AuthResponse, AuthState,
|
||||||
|
AuthStep, CURegState, CredentialDetailType, Entry, Filter, Modify, ModifyList, UatPurpose,
|
||||||
|
UserAuthToken,
|
||||||
};
|
};
|
||||||
use kanidmd_lib::credential::totp::Totp;
|
use kanidmd_lib::credential::totp::Totp;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
@ -13,7 +15,7 @@ use compact_jwt::JwsUnverified;
|
||||||
use webauthn_authenticator_rs::softpasskey::SoftPasskey;
|
use webauthn_authenticator_rs::softpasskey::SoftPasskey;
|
||||||
use webauthn_authenticator_rs::WebauthnAuthenticator;
|
use webauthn_authenticator_rs::WebauthnAuthenticator;
|
||||||
|
|
||||||
use kanidm_client::KanidmClient;
|
use kanidm_client::{ClientError, KanidmClient};
|
||||||
use kanidmd_testkit::ADMIN_TEST_PASSWORD;
|
use kanidmd_testkit::ADMIN_TEST_PASSWORD;
|
||||||
|
|
||||||
const UNIX_TEST_PASSWORD: &str = "unix test user password";
|
const UNIX_TEST_PASSWORD: &str = "unix test user password";
|
||||||
|
@ -1221,6 +1223,67 @@ async fn setup_demo_account_passkey(rsclient: &KanidmClient) -> WebauthnAuthenti
|
||||||
wa
|
wa
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn setup_demo_account_password(
|
||||||
|
rsclient: &KanidmClient,
|
||||||
|
) -> Result<(String, String), ClientError> {
|
||||||
|
let account_name = String::from_str("demo_account").expect("Failed to parse string");
|
||||||
|
|
||||||
|
let account_pass = String::from_str("eicieY7ahchaoCh0eeTa").expect("Failed to parse string");
|
||||||
|
|
||||||
|
rsclient
|
||||||
|
.auth_simple_password("admin", ADMIN_TEST_PASSWORD)
|
||||||
|
.await
|
||||||
|
.expect("Failed to authenticate as admin");
|
||||||
|
|
||||||
|
// Not recommended in production!
|
||||||
|
rsclient
|
||||||
|
.idm_group_add_members("idm_admins", &["admin"])
|
||||||
|
.await
|
||||||
|
.expect("Failed to add admin to idm_admins");
|
||||||
|
|
||||||
|
rsclient
|
||||||
|
.idm_person_account_create("demo_account", "Deeeeemo")
|
||||||
|
.await
|
||||||
|
.expect("Failed to create demo account");
|
||||||
|
|
||||||
|
// First, show there are no auth sessions.
|
||||||
|
let sessions = rsclient
|
||||||
|
.idm_account_list_user_auth_token("demo_account")
|
||||||
|
.await
|
||||||
|
.expect("Failed to list user auth tokens");
|
||||||
|
assert!(sessions.is_empty());
|
||||||
|
|
||||||
|
// Setup the credentials for the account
|
||||||
|
// Create an intent token for them
|
||||||
|
let intent_token = rsclient
|
||||||
|
.idm_person_account_credential_update_intent("demo_account", None)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create intent token");
|
||||||
|
|
||||||
|
// Logout, we don't need any auth now.
|
||||||
|
rsclient.logout().await.expect("Failed to logout");
|
||||||
|
|
||||||
|
// Exchange the intent token
|
||||||
|
let (session_token, _status) = rsclient
|
||||||
|
.idm_account_credential_update_exchange(intent_token)
|
||||||
|
.await
|
||||||
|
.expect("Failed to exchange intent token");
|
||||||
|
|
||||||
|
// Setup and update the password
|
||||||
|
rsclient
|
||||||
|
.idm_account_credential_update_set_password(&session_token, account_pass.as_str())
|
||||||
|
.await
|
||||||
|
.expect("Failed to set password");
|
||||||
|
|
||||||
|
// Commit it
|
||||||
|
rsclient
|
||||||
|
.idm_account_credential_update_commit(&session_token)
|
||||||
|
.await
|
||||||
|
.expect("Failed to commit changes");
|
||||||
|
|
||||||
|
Ok((account_name, account_pass))
|
||||||
|
}
|
||||||
|
|
||||||
#[kanidmd_testkit::test]
|
#[kanidmd_testkit::test]
|
||||||
async fn test_server_credential_update_session_passkey(rsclient: KanidmClient) {
|
async fn test_server_credential_update_session_passkey(rsclient: KanidmClient) {
|
||||||
let mut wa = setup_demo_account_passkey(&rsclient).await;
|
let mut wa = setup_demo_account_passkey(&rsclient).await;
|
||||||
|
@ -1491,3 +1554,131 @@ async fn test_privilege_expiry(rsclient: KanidmClient) {
|
||||||
let result = rsclient.system_auth_privilege_expiry_get().await.unwrap();
|
let result = rsclient.system_auth_privilege_expiry_get().await.unwrap();
|
||||||
assert_eq!(authsession_expiry, result);
|
assert_eq!(authsession_expiry, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn start_password_session(
|
||||||
|
rsclient: &KanidmClient,
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
privileged: bool,
|
||||||
|
) -> Result<UserAuthToken, ()> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let authreq = AuthRequest {
|
||||||
|
step: AuthStep::Init2 {
|
||||||
|
username: username.to_string(),
|
||||||
|
issue: AuthIssueSession::Token,
|
||||||
|
privileged,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let authreq = serde_json::to_string(&authreq).expect("Failed to serialize AuthRequest");
|
||||||
|
|
||||||
|
let res = match client
|
||||||
|
.post(rsclient.make_url("/v1/auth"))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(authreq)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => panic!("Failed to post: {:#?}", error),
|
||||||
|
};
|
||||||
|
assert_eq!(res.status(), 200);
|
||||||
|
|
||||||
|
let session_id = res.headers().get("x-kanidm-auth-session-id").unwrap();
|
||||||
|
|
||||||
|
let authreq = AuthRequest {
|
||||||
|
step: AuthStep::Begin(AuthMech::Password),
|
||||||
|
};
|
||||||
|
let authreq = serde_json::to_string(&authreq).expect("Failed to serialize AuthRequest");
|
||||||
|
|
||||||
|
let res = match client
|
||||||
|
.post(rsclient.make_url("/v1/auth"))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("x-kanidm-auth-session-id", session_id)
|
||||||
|
.body(authreq)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => panic!("Failed to post: {:#?}", error),
|
||||||
|
};
|
||||||
|
assert_eq!(res.status(), 200);
|
||||||
|
|
||||||
|
let authreq = AuthRequest {
|
||||||
|
step: AuthStep::Cred(AuthCredential::Password(password.to_string())),
|
||||||
|
};
|
||||||
|
let authreq = serde_json::to_string(&authreq).expect("Failed to serialize AuthRequest");
|
||||||
|
|
||||||
|
let res = match client
|
||||||
|
.post(rsclient.make_url("/v1/auth"))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("x-kanidm-auth-session-id", session_id)
|
||||||
|
.body(authreq)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => panic!("Failed to post: {:#?}", error),
|
||||||
|
};
|
||||||
|
assert_eq!(res.status(), 200);
|
||||||
|
|
||||||
|
let res: AuthResponse = res.json().await.expect("Failed to read JSON response");
|
||||||
|
let jwt = match res.state {
|
||||||
|
AuthState::Success(val) => val,
|
||||||
|
_ => panic!("Failed to extract jwt"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let jwt = JwsUnverified::from_str(&jwt).expect("Failed to parse jwt");
|
||||||
|
let uat: UserAuthToken = jwt
|
||||||
|
.validate_embeded()
|
||||||
|
.map(|jws| jws.into_inner())
|
||||||
|
.expect("Unable extract uat");
|
||||||
|
|
||||||
|
Ok(uat)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kanidmd_testkit::test]
|
||||||
|
async fn test_server_user_auth_unprivileged(rsclient: KanidmClient) {
|
||||||
|
let (account_name, account_pass) = setup_demo_account_password(&rsclient)
|
||||||
|
.await
|
||||||
|
.expect("Failed to setup demo_account");
|
||||||
|
|
||||||
|
let uat = start_password_session(
|
||||||
|
&rsclient,
|
||||||
|
account_name.as_str(),
|
||||||
|
account_pass.as_str(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to start session");
|
||||||
|
|
||||||
|
match uat.purpose {
|
||||||
|
UatPurpose::ReadOnly => panic!("Unexpected uat purpose"),
|
||||||
|
UatPurpose::ReadWrite { expiry } => {
|
||||||
|
assert!(expiry.is_none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kanidmd_testkit::test]
|
||||||
|
async fn test_server_user_auth_privileged_shortcut(rsclient: KanidmClient) {
|
||||||
|
let (account_name, account_pass) = setup_demo_account_password(&rsclient)
|
||||||
|
.await
|
||||||
|
.expect("Failed to setup demo_account");
|
||||||
|
|
||||||
|
let uat = start_password_session(
|
||||||
|
&rsclient,
|
||||||
|
account_name.as_str(),
|
||||||
|
account_pass.as_str(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Failed to start session");
|
||||||
|
|
||||||
|
match uat.purpose {
|
||||||
|
UatPurpose::ReadOnly => panic!("Unexpected uat purpose"),
|
||||||
|
UatPurpose::ReadWrite { expiry } => {
|
||||||
|
assert!(expiry.is_some())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -100,6 +100,7 @@ impl LoginApp {
|
||||||
step: AuthStep::Init2 {
|
step: AuthStep::Init2 {
|
||||||
username,
|
username,
|
||||||
issue: AuthIssueSession::Token,
|
issue: AuthIssueSession::Token,
|
||||||
|
privileged: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let req_jsvalue = serde_json::to_string(&authreq)
|
let req_jsvalue = serde_json::to_string(&authreq)
|
||||||
|
|
Loading…
Reference in a new issue