orca/models/
write.rs

1use crate::model::{self, ActorModel, Transition, TransitionAction, TransitionResult};
2
3use crate::error::Error;
4use crate::run::EventRecord;
5use crate::state::*;
6use kanidm_client::KanidmClient;
7
8use async_trait::async_trait;
9use rand::Rng;
10use rand_chacha::ChaCha8Rng;
11
12use std::time::Duration;
13
14enum State {
15    Unauthenticated,
16    Authenticated,
17    AuthenticatedWithReauth,
18}
19
20pub struct ActorWriter {
21    state: State,
22    randomised_backoff_time: Duration,
23}
24
25impl ActorWriter {
26    pub fn new(mut cha_rng: ChaCha8Rng, warmup_time_ms: u64) -> Self {
27        let max_backoff_time_in_ms = 2 * warmup_time_ms / 3;
28        let randomised_backoff_time =
29            Duration::from_millis(cha_rng.random_range(0..max_backoff_time_in_ms));
30        ActorWriter {
31            state: State::Unauthenticated,
32            randomised_backoff_time,
33        }
34    }
35}
36
37#[async_trait]
38impl ActorModel for ActorWriter {
39    async fn transition(
40        &mut self,
41        client: &KanidmClient,
42        person: &Person,
43    ) -> Result<Vec<EventRecord>, Error> {
44        let transition = self.next_transition();
45
46        if let Some(delay) = transition.delay {
47            tokio::time::sleep(delay).await;
48        }
49
50        // Once we get to here, we want the transition to go ahead.
51        let (result, event) = match transition.action {
52            TransitionAction::Login => model::login(client, person).await,
53            TransitionAction::Logout => model::logout(client, person).await,
54            TransitionAction::PrivilegeReauth => model::privilege_reauth(client, person).await,
55            TransitionAction::ReadSelfMemberOf
56            | TransitionAction::ReadSelfAccount
57            | TransitionAction::WriteSelfPassword => return Err(Error::InvalidState),
58            TransitionAction::WriteAttributePersonMail => {
59                let mail = format!("{}@example.com", person.username);
60                let values = &[mail.as_str()];
61                model::person_set_self_mail(client, person, values).await
62            }
63        }?;
64
65        self.next_state(transition.action, result);
66
67        Ok(event)
68    }
69}
70
71impl ActorWriter {
72    fn next_transition(&mut self) -> Transition {
73        match self.state {
74            State::Unauthenticated => Transition {
75                delay: Some(self.randomised_backoff_time),
76                action: TransitionAction::Login,
77            },
78            State::Authenticated => Transition {
79                delay: Some(Duration::from_secs(2)),
80                action: TransitionAction::PrivilegeReauth,
81            },
82            State::AuthenticatedWithReauth => Transition {
83                delay: Some(Duration::from_secs(1)),
84                action: TransitionAction::WriteAttributePersonMail,
85            },
86        }
87    }
88
89    fn next_state(&mut self, action: TransitionAction, result: TransitionResult) {
90        // Is this a design flaw? We probably need to know what the state was that we
91        // requested to move to?
92        match (&self.state, action, result) {
93            (State::Unauthenticated, TransitionAction::Login, TransitionResult::Ok) => {
94                self.state = State::Authenticated;
95            }
96            (State::Authenticated, TransitionAction::PrivilegeReauth, TransitionResult::Ok) => {
97                self.state = State::AuthenticatedWithReauth;
98            }
99            (
100                State::AuthenticatedWithReauth,
101                TransitionAction::WriteAttributePersonMail,
102                TransitionResult::Ok,
103            ) => self.state = State::AuthenticatedWithReauth,
104
105            #[allow(clippy::unreachable)]
106            (_, _, TransitionResult::Ok) => {
107                unreachable!();
108            }
109            (_, _, TransitionResult::Error) => {
110                self.state = State::Unauthenticated;
111            }
112        }
113    }
114}