use crate::model::{self, ActorModel, ActorRole, Transition, TransitionAction, TransitionResult};
use crate::error::Error;
use crate::run::EventRecord;
use crate::state::*;
use kanidm_client::KanidmClient;
use async_trait::async_trait;
use rand::Rng;
use rand_chacha::ChaCha8Rng;
use std::collections::BTreeSet;
use std::time::Duration;
enum State {
Unauthenticated,
Authenticated,
AuthenticatedWithReauth,
}
pub struct ActorBasic {
state: State,
randomised_backoff_time: Duration,
}
impl ActorBasic {
pub fn new(mut cha_rng: ChaCha8Rng, warmup_time_ms: u64) -> Self {
let max_backoff_time_in_ms = 2 * warmup_time_ms / 3;
let randomised_backoff_time =
Duration::from_millis(cha_rng.gen_range(0..max_backoff_time_in_ms));
ActorBasic {
state: State::Unauthenticated,
randomised_backoff_time,
}
}
}
#[async_trait]
impl ActorModel for ActorBasic {
async fn transition(
&mut self,
client: &KanidmClient,
person: &Person,
) -> Result<Vec<EventRecord>, Error> {
let transition = self.next_transition(&person.roles);
if let Some(delay) = transition.delay {
tokio::time::sleep(delay).await;
}
let (result, event) = match transition.action {
TransitionAction::Login => model::login(client, person).await,
TransitionAction::Logout => model::logout(client, person).await,
TransitionAction::PrivilegeReauth => model::privilege_reauth(client, person).await,
TransitionAction::WriteAttributePersonMail => {
let mail = format!("{}@example.com", person.username);
let values = &[mail.as_str()];
model::person_set_self_mail(client, person, values).await
}
TransitionAction::ReadSelfAccount => {
model::person_get_self_account(client, person).await
}
TransitionAction::ReadSelfMemberOf => {
model::person_get_self_memberof(client, person).await
}
TransitionAction::WriteSelfPassword => {
let Credential::Password { plain } = &person.credential;
model::person_set_self_password(client, person, plain).await
}
}?;
self.next_state(transition.action, result);
Ok(event)
}
}
impl ActorBasic {
fn next_transition(&mut self, roles: &BTreeSet<ActorRole>) -> Transition {
let logout_transition = Transition {
delay: Some(Duration::from_secs(5)),
action: TransitionAction::Logout,
};
match self.state {
State::Unauthenticated => Transition {
delay: Some(self.randomised_backoff_time),
action: TransitionAction::Login,
},
State::Authenticated => Transition {
delay: Some(Duration::from_secs(2)),
action: TransitionAction::PrivilegeReauth,
},
State::AuthenticatedWithReauth => match roles.first() {
Some(role) => match role {
ActorRole::PeopleSelfMailWrite => Transition {
delay: Some(Duration::from_secs(5)),
action: TransitionAction::WriteAttributePersonMail,
},
ActorRole::PeopleSelfReadProfile => Transition {
delay: Some(Duration::from_secs(2)),
action: TransitionAction::ReadSelfAccount,
},
ActorRole::PeopleSelfReadMemberOf => Transition {
delay: Some(Duration::from_secs(1)),
action: TransitionAction::ReadSelfMemberOf,
},
ActorRole::PeopleSelfSetPassword => Transition {
delay: Some(Duration::from_secs(3)),
action: TransitionAction::WriteSelfPassword,
},
ActorRole::PeoplePiiReader | ActorRole::PeopleGroupAdmin | ActorRole::None => {
logout_transition
}
},
None => logout_transition,
},
}
}
fn next_state(&mut self, action: TransitionAction, result: TransitionResult) {
match (&self.state, action, result) {
(State::Unauthenticated, TransitionAction::Login, TransitionResult::Ok) => {
self.state = State::Authenticated;
}
(State::Authenticated, TransitionAction::PrivilegeReauth, TransitionResult::Ok) => {
self.state = State::AuthenticatedWithReauth;
}
(
State::AuthenticatedWithReauth,
TransitionAction::WriteAttributePersonMail
| TransitionAction::ReadSelfAccount
| TransitionAction::ReadSelfMemberOf
| TransitionAction::WriteSelfPassword,
TransitionResult::Ok,
) => {
self.state = State::AuthenticatedWithReauth;
}
(_, TransitionAction::Logout, TransitionResult::Ok) => {
self.state = State::Unauthenticated;
}
#[allow(clippy::unreachable)]
(_, _, TransitionResult::Ok) => {
unreachable!();
}
(_, _, TransitionResult::Error) => {
self.state = State::Unauthenticated;
}
}
}
}