1use crate::error::Error;
2use crate::model::{ActorModel, ActorRole};
3use crate::models;
4use crate::profile::Profile;
5use core::fmt::Display;
6use kanidm_client::KanidmClient;
7use rand::SeedableRng;
8use rand_chacha::ChaCha8Rng;
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeSet;
11use std::path::Path;
12use std::time::Duration;
13#[derive(Debug, Serialize, Deserialize)]
19pub struct State {
20 pub profile: Profile,
21 pub preflight_flags: Vec<Flag>,
23 pub persons: Vec<Person>,
24 pub groups: Vec<Group>,
25 pub thread_count: Option<usize>, }
27
28impl State {
29 pub fn write_to_path(&self, path: &Path) -> Result<(), Error> {
30 let output = std::fs::File::create(path).map_err(|io_err| {
31 error!(?io_err);
32 Error::Io
33 })?;
34
35 serde_json::to_writer(output, self).map_err(|json_err| {
36 error!(?json_err);
37 Error::SerdeJson
38 })
39 }
40}
41
42impl TryFrom<&Path> for State {
43 type Error = Error;
44
45 fn try_from(path: &Path) -> Result<Self, Self::Error> {
46 let input = std::fs::File::open(path).map_err(|io_err| {
47 error!(?io_err);
48 Error::Io
49 })?;
50
51 serde_json::from_reader(input).map_err(|json_err| {
52 error!(?json_err);
53 Error::SerdeJson
54 })
55 }
56}
57
58#[derive(Debug, Serialize, Deserialize)]
59pub enum Flag {
60 DisableAllPersonsMFAPolicy,
61 ExtendPrivilegedAuthExpiry,
62}
63
64#[derive(Default, Debug, Serialize, Deserialize)]
65pub enum PreflightState {
66 #[default]
67 Present,
68 Absent,
69}
70
71#[derive(clap::ValueEnum, Debug, Serialize, Deserialize, Clone, Default, Copy)]
81#[serde(rename_all = "snake_case")]
82pub enum Model {
83 AuthOnly,
85 #[default]
87 Basic,
88 Reader,
90 Writer,
92 LatencyMeasurer,
94}
95
96impl Model {
97 pub fn as_dyn_object(
98 self,
99 rng_seed: u64,
100 additional_clients: Vec<KanidmClient>,
101 person_name: &str,
102 warmup_time: Duration,
103 ) -> Result<Box<dyn ActorModel + Send + '_>, Error> {
104 let cha_rng = ChaCha8Rng::seed_from_u64(rng_seed);
105 let warmup_time_as_ms = warmup_time.as_millis() as u64;
106 Ok(match self {
107 Model::AuthOnly => Box::new(models::auth_only::ActorAuthOnly::new()),
108 Model::Basic => Box::new(models::basic::ActorBasic::new(cha_rng, warmup_time_as_ms)),
109 Model::Reader => Box::new(models::read::ActorReader::new(cha_rng, warmup_time_as_ms)),
110 Model::Writer => Box::new(models::write::ActorWriter::new(cha_rng, warmup_time_as_ms)),
111 Model::LatencyMeasurer => {
112 Box::new(models::latency_measurer::ActorLatencyMeasurer::new(
113 cha_rng,
114 additional_clients,
115 person_name,
116 warmup_time_as_ms,
117 )?)
118 }
119 })
120 }
121}
122
123#[derive(Debug, Serialize, Deserialize)]
124pub enum Credential {
125 Password { plain: String },
126}
127
128#[derive(Debug, Serialize, Deserialize)]
129pub struct Person {
130 pub preflight_state: PreflightState,
131 pub username: String,
132 pub display_name: String,
133 pub roles: BTreeSet<ActorRole>,
134 pub credential: Credential,
135 pub model: Model,
136}
137
138#[derive(Default, Debug, Serialize, Deserialize)]
139pub struct Group {
140 pub name: GroupName,
141 pub preflight_state: PreflightState,
142 pub role: ActorRole,
143 pub members: BTreeSet<String>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, Hash, Default, Ord, Eq, PartialEq, PartialOrd)]
147#[serde(rename_all = "snake_case")]
148#[allow(clippy::enum_variant_names)]
149pub enum GroupName {
150 RolePeopleSelfSetPassword,
151 #[default]
152 RolePeoplePiiReader,
153 RolePeopleSelfMailWrite,
154 RolePeopleSelfReadProfile,
155 RolePeopleSelfReadMemberOf,
156 RolePeopleGroupAdmin,
157}
158
159impl Display for GroupName {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 write!(
162 f,
163 "{}",
164 toml::to_string(self)
165 .expect("Failed to parse group name as string")
166 .trim_matches('"')
167 )
168 }
169}
170
171impl TryFrom<&String> for GroupName {
172 type Error = toml::de::Error;
173
174 fn try_from(value: &String) -> Result<Self, Self::Error> {
175 toml::from_str(&format!("\"{value}\""))
176 }
177}
178
179#[cfg(test)]
180mod test {
181
182 use super::GroupName;
183
184 #[test]
185 fn test_group_names_parsing() {
186 let group_names = vec![
187 GroupName::RolePeopleGroupAdmin,
188 GroupName::RolePeoplePiiReader,
189 GroupName::RolePeopleSelfReadMemberOf,
190 ];
191 for name in group_names {
192 let str = name.to_string();
193 let parsed_group_name = GroupName::try_from(&str).expect("Failed to parse group name");
194
195 assert_eq!(parsed_group_name, name);
196 dbg!(str);
197 }
198 }
199
200 #[test]
201 fn test_group_name_from_str() {
202 let group_admin = "role_people_group_admin";
203 assert_eq!(
204 GroupName::RolePeopleGroupAdmin,
205 GroupName::try_from(&group_admin.to_string()).unwrap()
206 )
207 }
208}