2022-10-01 08:08:51 +02:00
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
use compact_jwt::{Jws, JwsSigner};
|
2023-02-24 04:43:19 +01:00
|
|
|
use kanidm_proto::v1::ApiToken as ProtoApiToken;
|
2022-10-01 08:08:51 +02:00
|
|
|
use time::OffsetDateTime;
|
|
|
|
|
2023-05-25 02:47:39 +02:00
|
|
|
use crate::credential::Credential;
|
2022-09-25 03:21:30 +02:00
|
|
|
use crate::event::SearchEvent;
|
|
|
|
use crate::idm::account::Account;
|
2023-05-25 02:47:39 +02:00
|
|
|
use crate::idm::event::GeneratePasswordEvent;
|
2022-09-25 03:21:30 +02:00
|
|
|
use crate::idm::server::{IdmServerProxyReadTransaction, IdmServerProxyWriteTransaction};
|
|
|
|
use crate::prelude::*;
|
2023-05-25 02:47:39 +02:00
|
|
|
use crate::utils::password_from_random;
|
2023-02-24 04:43:19 +01:00
|
|
|
use crate::value::ApiToken;
|
2022-09-25 03:21:30 +02:00
|
|
|
|
|
|
|
// Need to add KID to es256 der for lookups ✅
|
|
|
|
|
|
|
|
// Need to generate the es256 on the account on modifies ✅
|
|
|
|
|
|
|
|
// Add migration to generate the es256 on startup at least once. ✅
|
|
|
|
|
|
|
|
// Create new valueset type to store sessions w_ labels ✅
|
|
|
|
|
|
|
|
// Able to lookup from KID to get service account
|
|
|
|
|
|
|
|
// Able to take token -> ident
|
|
|
|
// -- check still valid
|
|
|
|
|
|
|
|
// revoke
|
|
|
|
|
|
|
|
macro_rules! try_from_entry {
|
|
|
|
($value:expr) => {{
|
|
|
|
// Check the classes
|
2023-08-21 09:16:43 +02:00
|
|
|
if !$value.attribute_equality(
|
|
|
|
Attribute::Class.as_ref(),
|
|
|
|
&EntryClass::ServiceAccount.into(),
|
|
|
|
) {
|
2022-09-25 03:21:30 +02:00
|
|
|
return Err(OperationError::InvalidAccountState(
|
|
|
|
"Missing class: service account".to_string(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2023-09-12 03:47:24 +02:00
|
|
|
let spn = $value
|
|
|
|
.get_ava_single_proto_string(Attribute::Spn.as_ref())
|
|
|
|
.ok_or(OperationError::InvalidAccountState(
|
|
|
|
"Missing attribute: spn".to_string(),
|
|
|
|
))?;
|
2022-09-25 03:21:30 +02:00
|
|
|
|
|
|
|
let jws_key = $value
|
|
|
|
.get_ava_single_jws_key_es256("jws_es256_private_key")
|
|
|
|
.cloned()
|
|
|
|
.ok_or(OperationError::InvalidAccountState(
|
|
|
|
"Missing attribute: jws_es256_private_key".to_string(),
|
|
|
|
))?;
|
|
|
|
|
|
|
|
let api_tokens = $value
|
2023-02-24 04:43:19 +01:00
|
|
|
.get_ava_as_apitoken_map("api_token_session")
|
2022-09-25 03:21:30 +02:00
|
|
|
.cloned()
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
let valid_from = $value.get_ava_single_datetime("account_valid_from");
|
|
|
|
|
|
|
|
let expire = $value.get_ava_single_datetime("account_expire");
|
|
|
|
|
|
|
|
let uuid = $value.get_uuid().clone();
|
|
|
|
|
|
|
|
Ok(ServiceAccount {
|
|
|
|
spn,
|
|
|
|
uuid,
|
|
|
|
valid_from,
|
|
|
|
expire,
|
|
|
|
api_tokens,
|
|
|
|
jws_key,
|
|
|
|
})
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct ServiceAccount {
|
|
|
|
pub spn: String,
|
|
|
|
pub uuid: Uuid,
|
|
|
|
|
|
|
|
pub valid_from: Option<OffsetDateTime>,
|
|
|
|
pub expire: Option<OffsetDateTime>,
|
|
|
|
|
2023-02-24 04:43:19 +01:00
|
|
|
pub api_tokens: BTreeMap<Uuid, ApiToken>,
|
2022-09-25 03:21:30 +02:00
|
|
|
|
|
|
|
pub jws_key: JwsSigner,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ServiceAccount {
|
2022-10-01 08:08:51 +02:00
|
|
|
#[instrument(level = "debug", skip_all)]
|
2022-09-25 03:21:30 +02:00
|
|
|
pub(crate) fn try_from_entry_rw(
|
|
|
|
value: &Entry<EntrySealed, EntryCommitted>,
|
|
|
|
// qs: &mut QueryServerWriteTransaction,
|
|
|
|
) -> Result<Self, OperationError> {
|
2022-10-01 08:08:51 +02:00
|
|
|
// let groups = Group::try_from_account_entry_rw(value, qs)?;
|
|
|
|
try_from_entry!(value)
|
2022-09-25 03:21:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn check_api_token_valid(
|
|
|
|
ct: Duration,
|
2023-02-24 04:43:19 +01:00
|
|
|
apit: &ProtoApiToken,
|
2022-09-25 03:21:30 +02:00
|
|
|
entry: &Entry<EntrySealed, EntryCommitted>,
|
|
|
|
) -> bool {
|
|
|
|
let within_valid_window = Account::check_within_valid_time(
|
|
|
|
ct,
|
|
|
|
entry.get_ava_single_datetime("account_valid_from").as_ref(),
|
|
|
|
entry.get_ava_single_datetime("account_expire").as_ref(),
|
|
|
|
);
|
|
|
|
|
|
|
|
if !within_valid_window {
|
|
|
|
security_info!("Account has expired or is not yet valid, not allowing to proceed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the sessions.
|
|
|
|
let session_present = entry
|
2023-02-24 04:43:19 +01:00
|
|
|
.get_ava_as_apitoken_map("api_token_session")
|
2022-09-25 03:21:30 +02:00
|
|
|
.map(|session_map| session_map.get(&apit.token_id).is_some())
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
|
|
if session_present {
|
|
|
|
security_info!("A valid session value exists for this token");
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
let grace = apit.issued_at + GRACE_WINDOW;
|
2023-05-25 00:25:16 +02:00
|
|
|
let current = time::OffsetDateTime::UNIX_EPOCH + ct;
|
2022-09-25 03:21:30 +02:00
|
|
|
trace!(%grace, %current);
|
|
|
|
if current >= grace {
|
|
|
|
security_info!(
|
|
|
|
"The token grace window has passed, and no session exists. Assuming invalid."
|
|
|
|
);
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
security_info!("The token grace window is in effect. Assuming valid.");
|
|
|
|
true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct ListApiTokenEvent {
|
|
|
|
// Who initiated this?
|
|
|
|
pub ident: Identity,
|
2023-01-10 04:50:53 +01:00
|
|
|
// Who is it targeting?
|
2022-09-25 03:21:30 +02:00
|
|
|
pub target: Uuid,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct GenerateApiTokenEvent {
|
|
|
|
// Who initiated this?
|
|
|
|
pub ident: Identity,
|
2023-01-10 04:50:53 +01:00
|
|
|
// Who is it targeting?
|
2022-09-25 03:21:30 +02:00
|
|
|
pub target: Uuid,
|
|
|
|
// The label
|
|
|
|
pub label: String,
|
|
|
|
// When should it expire?
|
|
|
|
pub expiry: Option<time::OffsetDateTime>,
|
2022-10-13 02:54:44 +02:00
|
|
|
// Is it read_write capable?
|
|
|
|
pub read_write: bool,
|
2022-09-25 03:21:30 +02:00
|
|
|
// Limits?
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GenerateApiTokenEvent {
|
|
|
|
#[cfg(test)]
|
|
|
|
pub fn new_internal(target: Uuid, label: &str, expiry: Option<Duration>) -> Self {
|
|
|
|
GenerateApiTokenEvent {
|
|
|
|
ident: Identity::from_internal(),
|
|
|
|
target,
|
|
|
|
label: label.to_string(),
|
2023-05-25 00:25:16 +02:00
|
|
|
expiry: expiry.map(|ct| time::OffsetDateTime::UNIX_EPOCH + ct),
|
2022-10-13 02:54:44 +02:00
|
|
|
read_write: false,
|
2022-09-25 03:21:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct DestroyApiTokenEvent {
|
|
|
|
// Who initiated this?
|
|
|
|
pub ident: Identity,
|
2023-01-10 04:50:53 +01:00
|
|
|
// Who is it targeting?
|
2022-09-25 03:21:30 +02:00
|
|
|
pub target: Uuid,
|
|
|
|
// Which token id.
|
|
|
|
pub token_id: Uuid,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DestroyApiTokenEvent {
|
|
|
|
#[cfg(test)]
|
|
|
|
pub fn new_internal(target: Uuid, token_id: Uuid) -> Self {
|
|
|
|
DestroyApiTokenEvent {
|
|
|
|
ident: Identity::from_internal(),
|
|
|
|
target,
|
|
|
|
token_id,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> IdmServerProxyWriteTransaction<'a> {
|
|
|
|
pub fn service_account_generate_api_token(
|
2022-10-24 01:50:31 +02:00
|
|
|
&mut self,
|
2022-09-25 03:21:30 +02:00
|
|
|
gte: &GenerateApiTokenEvent,
|
|
|
|
ct: Duration,
|
|
|
|
) -> Result<String, OperationError> {
|
|
|
|
let service_account = self
|
|
|
|
.qs_write
|
2022-12-18 04:26:20 +01:00
|
|
|
.internal_search_uuid(gte.target)
|
2022-09-25 03:21:30 +02:00
|
|
|
.and_then(|account_entry| ServiceAccount::try_from_entry_rw(&account_entry))
|
|
|
|
.map_err(|e| {
|
|
|
|
admin_error!(?e, "Failed to search service account");
|
|
|
|
e
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let session_id = Uuid::new_v4();
|
2023-05-25 00:25:16 +02:00
|
|
|
let issued_at = time::OffsetDateTime::UNIX_EPOCH + ct;
|
2022-09-25 03:21:30 +02:00
|
|
|
|
2023-01-10 04:50:53 +01:00
|
|
|
// Normalise to UTC in case it was provided as something else.
|
2022-11-11 01:23:49 +01:00
|
|
|
let expiry = gte.expiry.map(|odt| odt.to_offset(time::UtcOffset::UTC));
|
2022-09-25 03:21:30 +02:00
|
|
|
|
2023-02-24 04:43:19 +01:00
|
|
|
let scope = if gte.read_write {
|
|
|
|
ApiTokenScope::ReadWrite
|
2022-10-13 02:54:44 +02:00
|
|
|
} else {
|
2023-02-24 04:43:19 +01:00
|
|
|
ApiTokenScope::ReadOnly
|
2022-10-13 02:54:44 +02:00
|
|
|
};
|
2023-02-24 04:43:19 +01:00
|
|
|
let purpose = scope.try_into()?;
|
2022-10-13 02:54:44 +02:00
|
|
|
|
2022-09-25 03:21:30 +02:00
|
|
|
// create a new session
|
2023-02-24 04:43:19 +01:00
|
|
|
let session = Value::ApiToken(
|
2022-09-25 03:21:30 +02:00
|
|
|
session_id,
|
2023-02-24 04:43:19 +01:00
|
|
|
ApiToken {
|
2022-09-25 03:21:30 +02:00
|
|
|
label: gte.label.clone(),
|
|
|
|
expiry,
|
|
|
|
// Need the other inner bits?
|
|
|
|
// for the gracewindow.
|
|
|
|
issued_at,
|
|
|
|
// Who actually created this?
|
|
|
|
issued_by: gte.ident.get_event_origin_id(),
|
2022-10-13 02:54:44 +02:00
|
|
|
// What is the access scope of this session? This is
|
|
|
|
// for auditing purposes.
|
2023-02-24 04:43:19 +01:00
|
|
|
scope,
|
2022-09-25 03:21:30 +02:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
// create the session token (not yet signed)
|
2023-02-24 04:43:19 +01:00
|
|
|
let token = Jws::new(ProtoApiToken {
|
2022-09-25 03:21:30 +02:00
|
|
|
account_id: service_account.uuid,
|
|
|
|
token_id: session_id,
|
|
|
|
label: gte.label.clone(),
|
2022-10-29 11:07:54 +02:00
|
|
|
expiry: gte.expiry,
|
2022-09-25 03:21:30 +02:00
|
|
|
issued_at,
|
2022-10-13 02:54:44 +02:00
|
|
|
purpose,
|
2022-09-25 03:21:30 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
// modify the account to put the session onto it.
|
|
|
|
let modlist = ModifyList::new_list(vec![Modify::Present(
|
2023-09-05 08:58:42 +02:00
|
|
|
Attribute::ApiTokenSession.into(),
|
2022-09-25 03:21:30 +02:00
|
|
|
session,
|
|
|
|
)]);
|
|
|
|
|
|
|
|
self.qs_write
|
|
|
|
.impersonate_modify(
|
|
|
|
// Filter as executed
|
2023-08-21 09:16:43 +02:00
|
|
|
&filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(gte.target))),
|
2022-09-25 03:21:30 +02:00
|
|
|
// Filter as intended (acp)
|
2023-08-21 09:16:43 +02:00
|
|
|
&filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(gte.target))),
|
2022-09-25 03:21:30 +02:00
|
|
|
&modlist,
|
|
|
|
// Provide the event to impersonate
|
|
|
|
>e.ident,
|
|
|
|
)
|
|
|
|
.and_then(|_| {
|
|
|
|
// The modify succeeded and was allowed, now sign the token for return.
|
|
|
|
token
|
|
|
|
.sign_embed_public_jwk(&service_account.jws_key)
|
|
|
|
.map(|jws_signed| jws_signed.to_string())
|
|
|
|
.map_err(|e| {
|
|
|
|
admin_error!(err = ?e, "Unable to sign api token");
|
|
|
|
OperationError::CryptographyError
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.map_err(|e| {
|
|
|
|
admin_error!("Failed to generate api token {:?}", e);
|
|
|
|
e
|
|
|
|
})
|
|
|
|
// Done!
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn service_account_destroy_api_token(
|
2022-10-24 01:50:31 +02:00
|
|
|
&mut self,
|
2022-09-25 03:21:30 +02:00
|
|
|
dte: &DestroyApiTokenEvent,
|
|
|
|
) -> Result<(), OperationError> {
|
|
|
|
// Delete the attribute with uuid.
|
|
|
|
let modlist = ModifyList::new_list(vec![Modify::Removed(
|
2023-09-05 08:58:42 +02:00
|
|
|
Attribute::ApiTokenSession.into(),
|
2022-09-25 03:21:30 +02:00
|
|
|
PartialValue::Refer(dte.token_id),
|
|
|
|
)]);
|
|
|
|
|
|
|
|
self.qs_write
|
|
|
|
.impersonate_modify(
|
|
|
|
// Filter as executed
|
|
|
|
&filter!(f_and!([
|
2023-08-21 09:16:43 +02:00
|
|
|
f_eq(Attribute::Uuid, PartialValue::Uuid(dte.target)),
|
|
|
|
f_eq(
|
|
|
|
Attribute::ApiTokenSession,
|
|
|
|
PartialValue::Refer(dte.token_id)
|
|
|
|
)
|
2022-09-25 03:21:30 +02:00
|
|
|
])),
|
|
|
|
// Filter as intended (acp)
|
|
|
|
&filter_all!(f_and!([
|
2023-08-21 09:16:43 +02:00
|
|
|
f_eq(Attribute::Uuid, PartialValue::Uuid(dte.target)),
|
|
|
|
f_eq(
|
|
|
|
Attribute::ApiTokenSession,
|
|
|
|
PartialValue::Refer(dte.token_id)
|
|
|
|
)
|
2022-09-25 03:21:30 +02:00
|
|
|
])),
|
|
|
|
&modlist,
|
|
|
|
// Provide the event to impersonate
|
|
|
|
&dte.ident,
|
|
|
|
)
|
|
|
|
.map_err(|e| {
|
|
|
|
admin_error!("Failed to destroy api token {:?}", e);
|
|
|
|
e
|
|
|
|
})
|
|
|
|
}
|
2023-05-25 02:47:39 +02:00
|
|
|
|
|
|
|
pub fn generate_service_account_password(
|
|
|
|
&mut self,
|
|
|
|
gpe: &GeneratePasswordEvent,
|
|
|
|
) -> Result<String, OperationError> {
|
|
|
|
// Generate a new random, long pw.
|
|
|
|
// Because this is generated, we can bypass policy checks!
|
|
|
|
let cleartext = password_from_random();
|
|
|
|
let ncred = Credential::new_generatedpassword_only(self.crypto_policy(), &cleartext)
|
|
|
|
.map_err(|e| {
|
|
|
|
admin_error!("Unable to generate password mod {:?}", e);
|
|
|
|
e
|
|
|
|
})?;
|
|
|
|
let vcred = Value::new_credential("primary", ncred);
|
|
|
|
// We need to remove other credentials too.
|
|
|
|
let modlist = ModifyList::new_list(vec![
|
|
|
|
m_purge("passkeys"),
|
|
|
|
m_purge("primary_credential"),
|
|
|
|
Modify::Present("primary_credential".into(), vcred),
|
|
|
|
]);
|
|
|
|
|
|
|
|
trace!(?modlist, "processing change");
|
|
|
|
// given the new credential generate a modify
|
|
|
|
// We use impersonate here to get the event from ae
|
|
|
|
self.qs_write
|
|
|
|
.impersonate_modify(
|
|
|
|
// Filter as executed
|
2023-08-21 09:16:43 +02:00
|
|
|
&filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(gpe.target))),
|
2023-05-25 02:47:39 +02:00
|
|
|
// Filter as intended (acp)
|
2023-08-21 09:16:43 +02:00
|
|
|
&filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(gpe.target))),
|
2023-05-25 02:47:39 +02:00
|
|
|
&modlist,
|
|
|
|
// Provide the event to impersonate
|
|
|
|
&gpe.ident,
|
|
|
|
)
|
|
|
|
.map(|_| cleartext)
|
|
|
|
.map_err(|e| {
|
|
|
|
admin_error!("Failed to generate account password {:?}", e);
|
|
|
|
e
|
|
|
|
})
|
|
|
|
}
|
2022-09-25 03:21:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> IdmServerProxyReadTransaction<'a> {
|
|
|
|
pub fn service_account_list_api_token(
|
2022-12-28 08:52:25 +01:00
|
|
|
&mut self,
|
2022-09-25 03:21:30 +02:00
|
|
|
lte: &ListApiTokenEvent,
|
2023-02-24 04:43:19 +01:00
|
|
|
) -> Result<Vec<ProtoApiToken>, OperationError> {
|
2022-09-25 03:21:30 +02:00
|
|
|
// Make an event from the request
|
|
|
|
let srch = match SearchEvent::from_target_uuid_request(
|
|
|
|
lte.ident.clone(),
|
|
|
|
lte.target,
|
|
|
|
&self.qs_read,
|
|
|
|
) {
|
|
|
|
Ok(s) => s,
|
|
|
|
Err(e) => {
|
2022-10-17 12:09:47 +02:00
|
|
|
admin_error!("Failed to begin service account api token list: {:?}", e);
|
2022-09-25 03:21:30 +02:00
|
|
|
return Err(e);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
match self.qs_read.search_ext(&srch) {
|
|
|
|
Ok(mut entries) => {
|
2022-10-13 02:54:44 +02:00
|
|
|
entries
|
2022-09-25 03:21:30 +02:00
|
|
|
.pop()
|
|
|
|
// get the first entry
|
|
|
|
.and_then(|e| {
|
|
|
|
let account_id = e.get_uuid();
|
|
|
|
// From the entry, turn it into the value
|
2023-02-24 04:43:19 +01:00
|
|
|
e.get_ava_as_apitoken_map("api_token_session").map(|smap| {
|
2022-09-25 03:21:30 +02:00
|
|
|
smap.iter()
|
2022-10-13 02:54:44 +02:00
|
|
|
.map(|(u, s)| {
|
|
|
|
s.scope
|
|
|
|
.try_into()
|
2023-02-24 04:43:19 +01:00
|
|
|
.map(|purpose| ProtoApiToken {
|
2022-10-13 02:54:44 +02:00
|
|
|
account_id,
|
|
|
|
token_id: *u,
|
|
|
|
label: s.label.clone(),
|
2022-10-29 11:07:54 +02:00
|
|
|
expiry: s.expiry,
|
|
|
|
issued_at: s.issued_at,
|
2022-10-13 02:54:44 +02:00
|
|
|
purpose,
|
|
|
|
})
|
|
|
|
.map_err(|e| {
|
|
|
|
admin_error!("Invalid api_token {}", u);
|
|
|
|
e
|
|
|
|
})
|
2022-09-25 03:21:30 +02:00
|
|
|
})
|
2022-10-13 02:54:44 +02:00
|
|
|
.collect::<Result<Vec<_>, _>>()
|
2022-09-25 03:21:30 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
// No matching entry? Return none.
|
2022-10-13 02:54:44 +02:00
|
|
|
Ok(Vec::new())
|
|
|
|
})
|
2022-09-25 03:21:30 +02:00
|
|
|
}
|
|
|
|
Err(e) => Err(e),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2022-10-01 08:08:51 +02:00
|
|
|
use std::str::FromStr;
|
|
|
|
use std::time::Duration;
|
2022-09-25 03:21:30 +02:00
|
|
|
|
|
|
|
use compact_jwt::{Jws, JwsUnverified};
|
|
|
|
use kanidm_proto::v1::ApiToken;
|
2022-10-01 08:08:51 +02:00
|
|
|
|
2022-10-17 12:09:47 +02:00
|
|
|
use super::{DestroyApiTokenEvent, GenerateApiTokenEvent};
|
2022-10-01 08:08:51 +02:00
|
|
|
use crate::event::CreateEvent;
|
|
|
|
use crate::idm::server::IdmServerTransaction;
|
2022-11-02 10:46:09 +01:00
|
|
|
use crate::prelude::*;
|
2022-10-24 01:50:31 +02:00
|
|
|
|
2022-09-25 03:21:30 +02:00
|
|
|
const TEST_CURRENT_TIME: u64 = 6000;
|
|
|
|
|
2022-11-02 10:46:09 +01:00
|
|
|
#[idm_test]
|
|
|
|
async fn test_idm_service_account_api_token(
|
|
|
|
idms: &IdmServer,
|
|
|
|
_idms_delayed: &mut IdmServerDelayed,
|
|
|
|
) {
|
|
|
|
let ct = Duration::from_secs(TEST_CURRENT_TIME);
|
|
|
|
let past_grc = Duration::from_secs(TEST_CURRENT_TIME + 1) + GRACE_WINDOW;
|
|
|
|
let exp = Duration::from_secs(TEST_CURRENT_TIME + 6000);
|
|
|
|
let post_exp = Duration::from_secs(TEST_CURRENT_TIME + 6010);
|
|
|
|
let mut idms_prox_write = idms.proxy_write(ct).await;
|
|
|
|
|
|
|
|
let testaccount_uuid = Uuid::new_v4();
|
|
|
|
|
|
|
|
let e1 = entry_init!(
|
2023-09-12 03:47:24 +02:00
|
|
|
(Attribute::Class, EntryClass::Object.to_value()),
|
|
|
|
(Attribute::Class, EntryClass::Account.to_value()),
|
|
|
|
(Attribute::Class, EntryClass::ServiceAccount.to_value()),
|
|
|
|
(Attribute::Name, Value::new_iname("test_account_only")),
|
|
|
|
(Attribute::Uuid, Value::Uuid(testaccount_uuid)),
|
|
|
|
(Attribute::Description, Value::new_utf8s("testaccount")),
|
|
|
|
(Attribute::DisplayName, Value::new_utf8s("testaccount"))
|
2022-11-02 10:46:09 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
let ce = CreateEvent::new_internal(vec![e1]);
|
|
|
|
let cr = idms_prox_write.qs_write.create(&ce);
|
|
|
|
assert!(cr.is_ok());
|
|
|
|
|
|
|
|
let gte = GenerateApiTokenEvent::new_internal(testaccount_uuid, "TestToken", Some(exp));
|
|
|
|
|
|
|
|
let api_token = idms_prox_write
|
|
|
|
.service_account_generate_api_token(>e, ct)
|
|
|
|
.expect("failed to generate new api token");
|
|
|
|
|
|
|
|
trace!(?api_token);
|
|
|
|
|
|
|
|
// Deserialise it.
|
|
|
|
let apitoken_unverified =
|
|
|
|
JwsUnverified::from_str(&api_token).expect("Failed to parse apitoken");
|
|
|
|
let apitoken_inner: Jws<ApiToken> = apitoken_unverified
|
|
|
|
.validate_embeded()
|
|
|
|
.expect("Embedded jwk not found");
|
|
|
|
let apitoken_inner = apitoken_inner.into_inner();
|
|
|
|
|
|
|
|
let ident = idms_prox_write
|
|
|
|
.validate_and_parse_token_to_ident(Some(&api_token), ct)
|
|
|
|
.expect("Unable to verify api token.");
|
|
|
|
|
|
|
|
assert!(ident.get_uuid() == Some(testaccount_uuid));
|
|
|
|
|
|
|
|
// Woohoo! Okay lets test the other edge cases.
|
|
|
|
|
|
|
|
// Check the expiry
|
|
|
|
assert!(
|
|
|
|
idms_prox_write
|
|
|
|
.validate_and_parse_token_to_ident(Some(&api_token), post_exp)
|
|
|
|
.expect_err("Should not succeed")
|
|
|
|
== OperationError::SessionExpired
|
|
|
|
);
|
|
|
|
|
|
|
|
// Delete session
|
|
|
|
let dte =
|
|
|
|
DestroyApiTokenEvent::new_internal(apitoken_inner.account_id, apitoken_inner.token_id);
|
|
|
|
assert!(idms_prox_write
|
|
|
|
.service_account_destroy_api_token(&dte)
|
|
|
|
.is_ok());
|
|
|
|
|
|
|
|
// Within gracewindow?
|
|
|
|
// This is okay, because we are within the gracewindow.
|
|
|
|
let ident = idms_prox_write
|
|
|
|
.validate_and_parse_token_to_ident(Some(&api_token), ct)
|
|
|
|
.expect("Unable to verify api token.");
|
|
|
|
assert!(ident.get_uuid() == Some(testaccount_uuid));
|
|
|
|
|
|
|
|
// Past gracewindow?
|
|
|
|
assert!(
|
|
|
|
idms_prox_write
|
|
|
|
.validate_and_parse_token_to_ident(Some(&api_token), past_grc)
|
|
|
|
.expect_err("Should not succeed")
|
|
|
|
== OperationError::SessionExpired
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(idms_prox_write.commit().is_ok());
|
2022-09-25 03:21:30 +02:00
|
|
|
}
|
|
|
|
}
|