orca/models/
basic.rs
1use crate::model::{self, ActorModel, ActorRole, 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::collections::BTreeSet;
13use std::time::Duration;
14
15enum State {
16 Unauthenticated,
17 Authenticated,
18 AuthenticatedWithReauth,
19}
20
21pub struct ActorBasic {
22 state: State,
23 randomised_backoff_time: Duration,
24}
25
26impl ActorBasic {
27 pub fn new(mut cha_rng: ChaCha8Rng, warmup_time_ms: u64) -> Self {
28 let max_backoff_time_in_ms = 2 * warmup_time_ms / 3;
29 let randomised_backoff_time =
30 Duration::from_millis(cha_rng.random_range(0..max_backoff_time_in_ms));
31 ActorBasic {
32 state: State::Unauthenticated,
33 randomised_backoff_time,
34 }
35 }
36}
37
38#[async_trait]
39impl ActorModel for ActorBasic {
40 async fn transition(
41 &mut self,
42 client: &KanidmClient,
43 person: &Person,
44 ) -> Result<Vec<EventRecord>, Error> {
45 let transition = self.next_transition(&person.roles);
46
47 if let Some(delay) = transition.delay {
48 tokio::time::sleep(delay).await;
49 }
50
51 let (result, event) = match transition.action {
53 TransitionAction::Login => model::login(client, person).await,
54 TransitionAction::Logout => model::logout(client, person).await,
55 TransitionAction::PrivilegeReauth => model::privilege_reauth(client, person).await,
56 TransitionAction::WriteAttributePersonMail => {
57 let mail = format!("{}@example.com", person.username);
58 let values = &[mail.as_str()];
59 model::person_set_self_mail(client, person, values).await
60 }
61 TransitionAction::ReadSelfAccount => {
62 model::person_get_self_account(client, person).await
63 }
64 TransitionAction::ReadSelfMemberOf => {
65 model::person_get_self_memberof(client, person).await
66 }
67 TransitionAction::WriteSelfPassword => {
68 let Credential::Password { plain } = &person.credential;
70 model::person_set_self_password(client, person, plain).await
71 }
72 }?;
73
74 self.next_state(transition.action, result);
75
76 Ok(event)
77 }
78}
79
80impl ActorBasic {
81 fn next_transition(&mut self, roles: &BTreeSet<ActorRole>) -> Transition {
82 let logout_transition = Transition {
83 delay: Some(Duration::from_secs(5)),
84 action: TransitionAction::Logout,
85 };
86 match self.state {
87 State::Unauthenticated => Transition {
88 delay: Some(self.randomised_backoff_time),
89 action: TransitionAction::Login,
90 },
91 State::Authenticated => Transition {
92 delay: Some(Duration::from_secs(2)),
93 action: TransitionAction::PrivilegeReauth,
94 },
95 State::AuthenticatedWithReauth => match roles.first() {
99 Some(role) => match role {
100 ActorRole::PeopleSelfMailWrite => Transition {
101 delay: Some(Duration::from_secs(5)),
102 action: TransitionAction::WriteAttributePersonMail,
103 },
104 ActorRole::PeopleSelfReadProfile => Transition {
105 delay: Some(Duration::from_secs(2)),
106 action: TransitionAction::ReadSelfAccount,
107 },
108 ActorRole::PeopleSelfReadMemberOf => Transition {
109 delay: Some(Duration::from_secs(1)),
110 action: TransitionAction::ReadSelfMemberOf,
111 },
112 ActorRole::PeopleSelfSetPassword => Transition {
113 delay: Some(Duration::from_secs(3)),
114 action: TransitionAction::WriteSelfPassword,
115 },
116 ActorRole::PeoplePiiReader | ActorRole::PeopleGroupAdmin | ActorRole::None => {
117 logout_transition
118 }
119 },
120 None => logout_transition,
121 },
122 }
123 }
124
125 fn next_state(&mut self, action: TransitionAction, result: TransitionResult) {
126 match (&self.state, action, result) {
129 (State::Unauthenticated, TransitionAction::Login, TransitionResult::Ok) => {
130 self.state = State::Authenticated;
131 }
132 (State::Authenticated, TransitionAction::PrivilegeReauth, TransitionResult::Ok) => {
133 self.state = State::AuthenticatedWithReauth;
134 }
135 (
136 State::AuthenticatedWithReauth,
137 TransitionAction::WriteAttributePersonMail
138 | TransitionAction::ReadSelfAccount
139 | TransitionAction::ReadSelfMemberOf
140 | TransitionAction::WriteSelfPassword,
141 TransitionResult::Ok,
142 ) => {
143 self.state = State::AuthenticatedWithReauth;
144 }
145 (_, TransitionAction::Logout, TransitionResult::Ok) => {
146 self.state = State::Unauthenticated;
147 }
148 #[allow(clippy::unreachable)]
149 (_, _, TransitionResult::Ok) => {
150 unreachable!();
151 }
152 (_, _, TransitionResult::Error) => {
153 self.state = State::Unauthenticated;
154 }
155 }
156 }
157}