20241017 3107 token ttl (#3114)

This commit is contained in:
Firstyear 2024-10-18 13:28:52 +10:00 committed by GitHub
parent 99a799d72a
commit 5a3e5f1e07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 69 additions and 37 deletions

View file

@ -1807,7 +1807,7 @@ impl KanidmClient {
pub async fn idm_account_credential_update_exchange(
&self,
intent_token: CUIntentToken,
intent_token: String,
) -> Result<(CUSessionToken, CUStatus), ClientError> {
// We don't need to send the UAT with these, which is why we use the different path.
self.perform_simple_post_request("/v1/credential/_exchange_intent", &intent_token)

View file

@ -64,6 +64,8 @@ impl TotpSecret {
#[derive(Debug, Serialize, Deserialize, ToSchema)]
pub struct CUIntentToken {
pub token: String,
#[serde(with = "time::serde::timestamp")]
pub expiry_time: time::OffsetDateTime,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]

View file

@ -17,8 +17,8 @@ use kanidmd_lib::{
filter::{Filter, FilterInvalid},
idm::account::DestroySessionTokenEvent,
idm::credupdatesession::{
CredentialUpdateIntentToken, CredentialUpdateSessionToken, InitCredentialUpdateEvent,
InitCredentialUpdateIntentEvent,
CredentialUpdateIntentTokenExchange, CredentialUpdateSessionToken,
InitCredentialUpdateEvent, InitCredentialUpdateIntentEvent,
},
idm::event::{GeneratePasswordEvent, RegenerateRadiusSecretEvent, UnixPasswordChangeEvent},
idm::oauth2::{
@ -669,6 +669,7 @@ impl QueryServerWriteV1 {
})
.map(|tok| CUIntentToken {
token: tok.intent_id,
expiry_time: tok.expiry_time,
})
}
@ -679,14 +680,12 @@ impl QueryServerWriteV1 {
)]
pub async fn handle_idmcredentialexchangeintent(
&self,
intent_token: CUIntentToken,
intent_id: String,
eventid: Uuid,
) -> Result<(CUSessionToken, CUStatus), OperationError> {
let ct = duration_from_epoch_now();
let mut idms_prox_write = self.idms.proxy_write(ct).await?;
let intent_token = CredentialUpdateIntentToken {
intent_id: intent_token.token,
};
let intent_token = CredentialUpdateIntentTokenExchange { intent_id };
// TODO: this is throwing a 500 error when a session is already in use, that seems bad?
idms_prox_write
.exchange_intent_credential_update(intent_token, ct)

View file

@ -1340,7 +1340,7 @@ pub async fn account_user_auth_token_delete(
pub async fn credential_update_exchange_intent(
State(state): State<ServerState>,
Extension(kopid): Extension<KOpId>,
Json(intent_token): Json<CUIntentToken>,
Json(intent_token): Json<String>,
) -> Result<Json<(CUSessionToken, CUStatus)>, WebError> {
state
.qe_w_ref

View file

@ -19,9 +19,9 @@ use std::fmt::{Display, Formatter};
use uuid::Uuid;
use kanidm_proto::internal::{
CUCredState, CUExtPortal, CUIntentToken, CURegState, CURegWarning, CURequest, CUSessionToken,
CUStatus, CredentialDetail, OperationError, PasskeyDetail, PasswordFeedback, TotpAlgo,
UserAuthToken, COOKIE_CU_SESSION_TOKEN,
CUCredState, CUExtPortal, CURegState, CURegWarning, CURequest, CUSessionToken, CUStatus,
CredentialDetail, OperationError, PasskeyDetail, PasswordFeedback, TotpAlgo, UserAuthToken,
COOKIE_CU_SESSION_TOKEN,
};
use crate::https::extractors::{DomainInfo, DomainInfoRead, VerifiedClientInformation};
@ -690,7 +690,7 @@ pub(crate) async fn view_reset_get(
// We have a reset token and want to create a new session
match state
.qe_w_ref
.handle_idmcredentialexchangeintent(CUIntentToken { token }, kopid.eventid)
.handle_idmcredentialexchangeintent(token, kopid.eventid)
.await
{
Ok((cu_session_token, cu_status)) => {

View file

@ -32,12 +32,12 @@ use compact_jwt::jwe::JweBuilder;
use super::accountpolicy::ResolvedAccountPolicy;
const MAXIMUM_CRED_UPDATE_TTL: Duration = Duration::from_secs(900);
// Minimum 5 minutes.
const MINIMUM_INTENT_TTL: Duration = Duration::from_secs(300);
// Default 1 hour.
const DEFAULT_INTENT_TTL: Duration = Duration::from_secs(3600);
// Default 1 day.
const MAXIMUM_INTENT_TTL: Duration = Duration::from_secs(86400);
// Minimum 5 minutes.
const MINIMUM_INTENT_TTL: Duration = Duration::from_secs(300);
#[derive(Debug)]
pub enum PasswordQuality {
@ -50,6 +50,20 @@ pub enum PasswordQuality {
#[derive(Clone, Debug)]
pub struct CredentialUpdateIntentToken {
pub intent_id: String,
pub expiry_time: OffsetDateTime,
}
#[derive(Clone, Debug)]
pub struct CredentialUpdateIntentTokenExchange {
pub intent_id: String,
}
impl From<CredentialUpdateIntentToken> for CredentialUpdateIntentTokenExchange {
fn from(tok: CredentialUpdateIntentToken) -> Self {
CredentialUpdateIntentTokenExchange {
intent_id: tok.intent_id,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
@ -932,8 +946,12 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
let mttl = event.max_ttl.unwrap_or(DEFAULT_INTENT_TTL);
let clamped_mttl = mttl.clamp(MINIMUM_INTENT_TTL, MAXIMUM_INTENT_TTL);
debug!(?clamped_mttl, "clamped update intent validity");
// Absolute expiry of the intent token in epoch seconds
let max_ttl = ct + clamped_mttl;
// Get the expiry of the intent token as an odt.
let expiry_time = OffsetDateTime::UNIX_EPOCH + max_ttl;
let intent_id = readable_password_from_random();
// Mark that we have created an intent token on the user.
@ -984,15 +1002,18 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
e
})?;
Ok(CredentialUpdateIntentToken { intent_id })
Ok(CredentialUpdateIntentToken {
intent_id,
expiry_time,
})
}
pub fn exchange_intent_credential_update(
&mut self,
token: CredentialUpdateIntentToken,
token: CredentialUpdateIntentTokenExchange,
current_time: Duration,
) -> Result<(CredentialUpdateSessionToken, CredentialUpdateSessionStatus), OperationError> {
let CredentialUpdateIntentToken { intent_id } = token;
let CredentialUpdateIntentTokenExchange { intent_id } = token;
/*
let entry = self.qs_write.internal_search_uuid(&token.target)?;
@ -2633,24 +2654,24 @@ mod tests {
// exchange intent token - invalid - fail
// Expired
let cur = idms_prox_write
.exchange_intent_credential_update(intent_tok.clone(), ct + MINIMUM_INTENT_TTL);
.exchange_intent_credential_update(intent_tok.clone().into(), ct + MINIMUM_INTENT_TTL);
assert!(matches!(cur, Err(OperationError::SessionExpired)));
let cur = idms_prox_write
.exchange_intent_credential_update(intent_tok.clone(), ct + MAXIMUM_INTENT_TTL);
.exchange_intent_credential_update(intent_tok.clone().into(), ct + MAXIMUM_INTENT_TTL);
assert!(matches!(cur, Err(OperationError::SessionExpired)));
// exchange intent token - success
let (cust_a, _c_status) = idms_prox_write
.exchange_intent_credential_update(intent_tok.clone(), ct)
.exchange_intent_credential_update(intent_tok.clone().into(), ct)
.unwrap();
// Session in progress - This will succeed and then block the former success from
// committing.
let (cust_b, _c_status) = idms_prox_write
.exchange_intent_credential_update(intent_tok, ct + Duration::from_secs(1))
.exchange_intent_credential_update(intent_tok.into(), ct + Duration::from_secs(1))
.unwrap();
let cur = idms_prox_write.commit_credential_update(&cust_a, ct);

View file

@ -1069,7 +1069,7 @@ async fn test_server_credential_update_session_pw(rsclient: KanidmClient) {
let _ = rsclient.logout().await;
// Exchange the intent token
let (session_token, _status) = rsclient
.idm_account_credential_update_exchange(intent_token)
.idm_account_credential_update_exchange(intent_token.token)
.await
.unwrap();
@ -1144,7 +1144,7 @@ async fn test_server_credential_update_session_totp_pw(rsclient: KanidmClient) {
let _ = rsclient.logout().await;
// Exchange the intent token
let (session_token, _statu) = rsclient
.idm_account_credential_update_exchange(intent_token)
.idm_account_credential_update_exchange(intent_token.token)
.await
.unwrap();
@ -1273,7 +1273,7 @@ async fn setup_demo_account_passkey(rsclient: &KanidmClient) -> WebauthnAuthenti
let _ = rsclient.logout().await;
// Exchange the intent token
let (session_token, _status) = rsclient
.idm_account_credential_update_exchange(intent_token)
.idm_account_credential_update_exchange(intent_token.token)
.await
.unwrap();
@ -1359,7 +1359,7 @@ async fn setup_demo_account_password(
// Exchange the intent token
let (session_token, _status) = rsclient
.idm_account_credential_update_exchange(intent_token)
.idm_account_credential_update_exchange(intent_token.token)
.await
.expect("Failed to exchange intent token");
@ -1611,7 +1611,7 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) {
let _ = rsclient.logout().await;
// Exchange the intent token
let (session_token, _status) = rsclient
.idm_account_credential_update_exchange(intent_token)
.idm_account_credential_update_exchange(intent_token.token)
.await
.unwrap();

View file

@ -816,7 +816,7 @@ impl CredentialResetApp {
}
async fn exchange_intent_token(token: String) -> Result<Msg, FetchError> {
let request = CUIntentToken { token };
let request = token;
let req_jsvalue = request
.serialize(&serde_wasm_bindgen::Serializer::json_compatible())
.expect("Failed to serialise request");

View file

@ -49,7 +49,7 @@ rpassword = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
shellexpand = { workspace = true }
time = { workspace = true, features = ["serde", "std"] }
time = { workspace = true, features = ["serde", "std", "local-offset"] }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] }
tokio = { workspace = true, features = ["rt", "macros", "fs", "signal"] }

View file

@ -21,7 +21,7 @@ use kanidm_proto::scim_v1::{client::ScimSshPublicKeys, ScimEntryGetQuery};
use qrcode::render::unicode;
use qrcode::QrCode;
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
use time::{OffsetDateTime, UtcOffset};
use uuid::Uuid;
use crate::webauthn::get_authenticator;
@ -637,9 +637,7 @@ impl AccountCredential {
// The account credential use_reset_token CLI
AccountCredential::UseResetToken(aopt) => {
let client = aopt.copt.to_unauth_client();
let cuintent_token = CUIntentToken {
token: aopt.token.clone(),
};
let cuintent_token = aopt.token.clone();
match client
.idm_account_credential_update_exchange(cuintent_token)
@ -669,14 +667,13 @@ impl AccountCredential {
.idm_person_account_credential_update_intent(aopts.account_id.as_str(), *ttl)
.await
{
Ok(cuintent_token) => {
Ok(CUIntentToken { token, expiry_time }) => {
let mut url = client.make_url("/ui/reset");
url.query_pairs_mut()
.append_pair("token", cuintent_token.token.as_str());
url.query_pairs_mut().append_pair("token", token.as_str());
debug!(
"Successfully created credential reset token for {}: {}",
aopts.account_id, cuintent_token.token
aopts.account_id, token
);
println!(
"The person can use one of the following to allow the credential reset"
@ -700,7 +697,19 @@ impl AccountCredential {
println!("This link: {}", url.as_str());
println!(
"Or run this command: kanidm person credential use-reset-token {}",
cuintent_token.token
token
);
// Now get the abs time
let local_offset =
UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
let expiry_time = expiry_time.to_offset(local_offset);
println!(
"This token will expire at: {}",
expiry_time
.format(&Rfc3339)
.expect("Failed to format date time!!!")
);
println!();
}

View file

@ -464,6 +464,7 @@ pub enum AccountCredential {
#[clap(flatten)]
copt: CommonOpt,
/// Optionally set how many seconds the reset token should be valid for.
/// Default: 3600 seconds
ttl: Option<u32>,
},
}