1use crate::common::try_expire_at_from_string;
2use kanidm_proto::cli::OpType;
3use kanidm_proto::constants::{
4 ATTR_ACCOUNT_EXPIRE, ATTR_ACCOUNT_VALID_FROM, ATTR_GIDNUMBER, ATTR_SSH_PUBLICKEY,
5};
6use kanidm_proto::messages::{AccountChangeMessage, ConsoleOutputMode, MessageStatus};
7use time::OffsetDateTime;
8
9use crate::{
10 handle_client_error, AccountSsh, AccountUserAuthToken, AccountValidity, KanidmClientParser,
11 OutputMode, ServiceAccountApiToken, ServiceAccountCredential, ServiceAccountOpt,
12 ServiceAccountPosix,
13};
14use time::format_description::well_known::Rfc3339;
15
16impl ServiceAccountOpt {
17 pub async fn exec(&self, opt: KanidmClientParser) {
18 match self {
19 ServiceAccountOpt::Credential { commands } => match commands {
20 ServiceAccountCredential::Status(apo) => {
21 let client = opt.to_client(OpType::Read).await;
22 match client
23 .idm_service_account_get_credential_status(apo.aopts.account_id.as_str())
24 .await
25 {
26 Ok(cstatus) => {
27 println!("{cstatus}");
28 }
29 Err(e) => {
30 error!("Error getting credential status -> {:?}", e);
31 }
32 }
33 }
34 ServiceAccountCredential::GeneratePw(apo) => {
35 let client = opt.to_client(OpType::Write).await;
36 match client
37 .idm_service_account_generate_password(apo.aopts.account_id.as_str())
38 .await
39 {
40 Ok(new_pw) => {
41 println!("Success: {new_pw}");
42 }
43 Err(e) => {
44 error!("Error generating service account credential -> {:?}", e);
45 }
46 }
47 }
48 }, ServiceAccountOpt::ApiToken { commands } => match commands {
50 ServiceAccountApiToken::Status(apo) => {
51 let client = opt.to_client(OpType::Read).await;
52 match client
53 .idm_service_account_list_api_token(apo.aopts.account_id.as_str())
54 .await
55 {
56 Ok(tokens) => {
57 if tokens.is_empty() {
58 println!("No api tokens exist");
59 } else {
60 for token in tokens {
61 println!("token: {token}");
62 }
63 }
64 }
65 Err(e) => {
66 error!("Error listing service account api tokens -> {:?}", e);
67 }
68 }
69 }
70 ServiceAccountApiToken::Generate {
71 aopts,
72 label,
73 expiry,
74 read_write,
75 } => {
76 let expiry_odt = if let Some(t) = expiry {
77 match OffsetDateTime::parse(t, &Rfc3339).map(|odt| {
79 odt.to_offset(
80 time::UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH)
81 .unwrap_or(time::UtcOffset::UTC),
82 )
83 }) {
84 Ok(odt) => {
85 debug!("valid until: {}", odt);
86 Some(odt)
87 }
88 Err(e) => {
89 error!("Error parsing expiry (input: {t:?}) -> {:?}", e);
90 return;
91 }
92 }
93 } else {
94 None
95 };
96
97 let client = opt.to_client(OpType::Write).await;
98
99 match client
100 .idm_service_account_generate_api_token(
101 aopts.account_id.as_str(),
102 label,
103 expiry_odt,
104 *read_write,
105 )
106 .await
107 {
108 Ok(new_token) => match opt.output_mode {
109 OutputMode::Json => {
110 let message = AccountChangeMessage {
111 output_mode: ConsoleOutputMode::JSON,
112 action: "api-token generate".to_string(),
113 result: new_token,
114 status: kanidm_proto::messages::MessageStatus::Success,
115 src_user: opt
116 .username
117 .clone()
118 .unwrap_or("<unknown username>".to_string()),
119 dest_user: aopts.account_id.clone(),
120 };
121 println!("{message}");
122 }
123 OutputMode::Text => {
124 println!("Success: This token will only be displayed ONCE");
125 println!("{new_token}")
126 }
127 },
128 Err(e) => {
129 error!("Error generating service account api token -> {:?}", e);
130 }
131 }
132 }
133 ServiceAccountApiToken::Destroy { aopts, token_id } => {
134 let client = opt.to_client(OpType::Write).await;
135 match client
136 .idm_service_account_destroy_api_token(aopts.account_id.as_str(), *token_id)
137 .await
138 {
139 Ok(()) => {
140 println!("Success");
141 }
142 Err(e) => {
143 error!("Error destroying service account token -> {:?}", e);
144 }
145 }
146 }
147 }, ServiceAccountOpt::Posix { commands } => match commands {
149 ServiceAccountPosix::Show(aopt) => {
150 let client = opt.to_client(OpType::Read).await;
151 match client
152 .idm_account_unix_token_get(aopt.aopts.account_id.as_str())
153 .await
154 {
155 Ok(token) => println!("{token}"),
156 Err(e) => handle_client_error(e, opt.output_mode),
157 }
158 }
159 ServiceAccountPosix::Set(aopt) => {
160 let client = opt.to_client(OpType::Write).await;
161 if let Err(e) = client
162 .idm_service_account_unix_extend(
163 aopt.aopts.account_id.as_str(),
164 aopt.gidnumber,
165 aopt.shell.as_deref(),
166 )
167 .await
168 {
169 handle_client_error(e, opt.output_mode)
170 }
171 }
172 ServiceAccountPosix::ResetGidnumber { account_id } => {
173 let client = opt.to_client(OpType::Write).await;
174 if let Err(e) = client
175 .idm_service_account_purge_attr(account_id.as_str(), ATTR_GIDNUMBER)
176 .await
177 {
178 handle_client_error(e, opt.output_mode)
179 }
180 }
181 }, ServiceAccountOpt::Session { commands } => match commands {
183 AccountUserAuthToken::Status(apo) => {
184 let client = opt.to_client(OpType::Read).await;
185 match client
186 .idm_account_list_user_auth_token(apo.aopts.account_id.as_str())
187 .await
188 {
189 Ok(tokens) => {
190 if tokens.is_empty() {
191 println!("No sessions exist");
192 } else {
193 for token in tokens {
194 println!("token: {token}");
195 }
196 }
197 }
198 Err(e) => {
199 error!("Error listing sessions -> {:?}", e);
200 }
201 }
202 }
203 AccountUserAuthToken::Destroy { aopts, session_id } => {
204 let client = opt.to_client(OpType::Write).await;
205 match client
206 .idm_account_destroy_user_auth_token(aopts.account_id.as_str(), *session_id)
207 .await
208 {
209 Ok(()) => {
210 println!("Success");
211 }
212 Err(e) => {
213 error!("Error destroying account session -> {:?}", e);
214 }
215 }
216 }
217 }, ServiceAccountOpt::Ssh { commands } => match commands {
219 AccountSsh::List(aopt) => {
220 let client = opt.to_client(OpType::Read).await;
221
222 match client
223 .idm_service_account_get_attr(
224 aopt.aopts.account_id.as_str(),
225 ATTR_SSH_PUBLICKEY,
226 )
227 .await
228 {
229 Ok(pkeys) => pkeys.iter().flatten().for_each(|pkey| println!("{pkey}")),
230 Err(e) => handle_client_error(e, opt.output_mode),
231 }
232 }
233 AccountSsh::Add(aopt) => {
234 let client = opt.to_client(OpType::Write).await;
235 if let Err(e) = client
236 .idm_service_account_post_ssh_pubkey(
237 aopt.aopts.account_id.as_str(),
238 aopt.tag.as_str(),
239 aopt.pubkey.as_str(),
240 )
241 .await
242 {
243 handle_client_error(e, opt.output_mode)
244 }
245 }
246 AccountSsh::Delete(aopt) => {
247 let client = opt.to_client(OpType::Write).await;
248 if let Err(e) = client
249 .idm_service_account_delete_ssh_pubkey(
250 aopt.aopts.account_id.as_str(),
251 aopt.tag.as_str(),
252 )
253 .await
254 {
255 handle_client_error(e, opt.output_mode)
256 }
257 }
258 }, ServiceAccountOpt::List => {
260 let client = opt.to_client(OpType::Read).await;
261 match client.idm_service_account_list().await {
262 Ok(r) => r.iter().for_each(|ent| println!("{ent}")),
263 Err(e) => handle_client_error(e, opt.output_mode),
264 }
265 }
266 ServiceAccountOpt::Update(aopt) => {
267 let client = opt.to_client(OpType::Write).await;
268 match client
269 .idm_service_account_update(
270 aopt.aopts.account_id.as_str(),
271 aopt.newname.as_deref(),
272 aopt.displayname.as_deref(),
273 aopt.entry_managed_by.as_deref(),
274 aopt.mail.as_deref(),
275 )
276 .await
277 {
278 Ok(()) => println!("Success"),
279 Err(e) => handle_client_error(e, opt.output_mode),
280 }
281 }
282 ServiceAccountOpt::Get(aopt) => {
283 let client = opt.to_client(OpType::Read).await;
284 let res = client
285 .idm_service_account_get(aopt.aopts.account_id.as_str())
286 .await;
287 match res {
288 Ok(Some(e)) => opt.output_mode.print_message(e),
289 Ok(None) => opt.output_mode.print_message("No matching entries"),
290 Err(e) => handle_client_error(e, opt.output_mode),
291 }
292 }
293 ServiceAccountOpt::Delete(aopt) => {
294 let client = opt.to_client(OpType::Write).await;
295 let mut modmessage = AccountChangeMessage {
296 output_mode: ConsoleOutputMode::Text,
297 action: "account delete".to_string(),
298 result: "deleted".to_string(),
299 src_user: opt
300 .username
301 .to_owned()
302 .unwrap_or(format!("{:?}", client.whoami().await)),
303 dest_user: aopt.aopts.account_id.to_string(),
304 status: MessageStatus::Success,
305 };
306 match client
307 .idm_service_account_delete(aopt.aopts.account_id.as_str())
308 .await
309 {
310 Err(e) => {
311 modmessage.result = format!("Error -> {e:?}");
312 modmessage.status = MessageStatus::Failure;
313 eprintln!("{modmessage}");
314 }
315 Ok(result) => {
316 debug!("{:?}", result);
317 println!("{modmessage}");
318 }
319 };
320 }
321 ServiceAccountOpt::Create {
322 aopts,
323 display_name,
324 entry_managed_by,
325 } => {
326 let client = opt.to_client(OpType::Write).await;
327 if let Err(e) = client
328 .idm_service_account_create(
329 aopts.account_id.as_str(),
330 display_name.as_str(),
331 entry_managed_by.as_str(),
332 )
333 .await
334 {
335 handle_client_error(e, opt.output_mode)
336 }
337 }
338 ServiceAccountOpt::Validity { commands } => match commands {
339 AccountValidity::Show(ano) => {
340 let client = opt.to_client(OpType::Read).await;
341
342 let entry = match client
343 .idm_service_account_get(ano.aopts.account_id.as_str())
344 .await
345 {
346 Err(err) => {
347 error!(
348 "No account {} found, or other error occurred: {:?}",
349 ano.aopts.account_id.as_str(),
350 err
351 );
352 return;
353 }
354 Ok(val) => match val {
355 Some(val) => val,
356 None => {
357 error!("No account {} found!", ano.aopts.account_id.as_str());
358 return;
359 }
360 },
361 };
362
363 println!("user: {}", ano.aopts.account_id.as_str());
364 if let Some(t) = entry.attrs.get(ATTR_ACCOUNT_VALID_FROM) {
365 let t = OffsetDateTime::parse(&t[0], &Rfc3339)
367 .map(|odt| {
368 odt.to_offset(
369 time::UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH)
370 .unwrap_or(time::UtcOffset::UTC),
371 )
372 .format(&Rfc3339)
373 .unwrap_or(odt.to_string())
374 })
375 .unwrap_or_else(|_| "invalid timestamp".to_string());
376
377 println!("valid after: {t}");
378 } else {
379 println!("valid after: any time");
380 }
381
382 if let Some(t) = entry.attrs.get(ATTR_ACCOUNT_EXPIRE) {
383 let t = OffsetDateTime::parse(&t[0], &Rfc3339)
384 .map(|odt| {
385 odt.to_offset(
386 time::UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH)
387 .unwrap_or(time::UtcOffset::UTC),
388 )
389 .format(&Rfc3339)
390 .unwrap_or(odt.to_string())
391 })
392 .unwrap_or_else(|_| "invalid timestamp".to_string());
393 println!("expire: {t:?}");
394 } else {
395 println!("expire: never");
396 }
397 }
398 AccountValidity::ExpireAt(ano) => {
399 let client = opt.to_client(OpType::Write).await;
400 let validity = match try_expire_at_from_string(ano.datetime.as_str()) {
401 Ok(val) => val,
402 Err(()) => return,
403 };
404 let res = match validity {
405 None => {
406 client
407 .idm_service_account_purge_attr(
408 ano.aopts.account_id.as_str(),
409 ATTR_ACCOUNT_EXPIRE,
410 )
411 .await
412 }
413 Some(new_expiry) => {
414 client
415 .idm_service_account_set_attr(
416 ano.aopts.account_id.as_str(),
417 ATTR_ACCOUNT_EXPIRE,
418 &[&new_expiry],
419 )
420 .await
421 }
422 };
423 match res {
424 Err(e) => handle_client_error(e, opt.output_mode),
425 _ => println!("Success"),
426 };
427 }
428 AccountValidity::BeginFrom(ano) => {
429 let client = opt.to_client(OpType::Write).await;
430 if matches!(ano.datetime.as_str(), "any" | "clear" | "whenever") {
431 match client
433 .idm_service_account_purge_attr(
434 ano.aopts.account_id.as_str(),
435 ATTR_ACCOUNT_VALID_FROM,
436 )
437 .await
438 {
439 Err(e) => handle_client_error(e, opt.output_mode),
440 _ => println!("Success"),
441 }
442 } else {
443 if let Err(e) = OffsetDateTime::parse(ano.datetime.as_str(), &Rfc3339) {
445 error!("Error -> {:?}", e);
446 return;
447 }
448
449 match client
450 .idm_service_account_set_attr(
451 ano.aopts.account_id.as_str(),
452 ATTR_ACCOUNT_VALID_FROM,
453 &[ano.datetime.as_str()],
454 )
455 .await
456 {
457 Err(e) => handle_client_error(e, opt.output_mode),
458 _ => println!("Success"),
459 }
460 }
461 }
462 }, ServiceAccountOpt::IntoPerson(aopt) => {
464 warn!("This command is deprecated and will be removed in a future release");
465 let client = opt.to_client(OpType::Write).await;
466 match client
467 .idm_service_account_into_person(aopt.aopts.account_id.as_str())
468 .await
469 {
470 Ok(()) => println!("Success"),
471 Err(e) => handle_client_error(e, opt.output_mode),
472 }
473 }
474 }
475 }
476}