kanidmd_core/actors/
v1_write.rs

1use std::iter;
2
3use compact_jwt::JweCompact;
4use kanidm_proto::internal::{
5    CUIntentToken, CUSessionToken, CUStatus, CreateRequest, DeleteRequest, ImageValue,
6    Modify as ProtoModify, ModifyList as ProtoModifyList, ModifyRequest,
7    Oauth2ClaimMapJoin as ProtoOauth2ClaimMapJoin, OperationError,
8};
9use kanidm_proto::v1::{AccountUnixExtend, Entry as ProtoEntry, GroupUnixExtend};
10use std::str::FromStr;
11use time::OffsetDateTime;
12use tracing::{info, instrument, trace};
13use uuid::Uuid;
14
15use kanidmd_lib::{
16    event::{CreateEvent, DeleteEvent, ModifyEvent, ReviveRecycledEvent},
17    filter::{Filter, FilterInvalid},
18    idm::account::DestroySessionTokenEvent,
19    idm::credupdatesession::{
20        CredentialUpdateIntentTokenExchange, CredentialUpdateSessionToken,
21        InitCredentialUpdateEvent, InitCredentialUpdateIntentEvent,
22    },
23    idm::event::{GeneratePasswordEvent, RegenerateRadiusSecretEvent, UnixPasswordChangeEvent},
24    idm::oauth2::{
25        AccessTokenRequest, AccessTokenResponse, AuthorisePermitSuccess, Oauth2Error,
26        TokenRevokeRequest,
27    },
28    idm::server::IdmServerTransaction,
29    idm::serviceaccount::{DestroyApiTokenEvent, GenerateApiTokenEvent},
30    modify::{Modify, ModifyInvalid, ModifyList},
31    value::{OauthClaimMapJoin, PartialValue, Value},
32};
33
34use kanidmd_lib::prelude::*;
35
36#[cfg(feature = "dev-oauth2-device-flow")]
37use std::collections::BTreeSet;
38
39use super::QueryServerWriteV1;
40
41impl QueryServerWriteV1 {
42    #[instrument(level = "debug", skip_all)]
43    async fn modify_from_parts(
44        &self,
45        client_auth_info: ClientAuthInfo,
46        uuid_or_name: &str,
47        proto_ml: &ProtoModifyList,
48        filter: Filter<FilterInvalid>,
49    ) -> Result<(), OperationError> {
50        let ct = duration_from_epoch_now();
51        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
52
53        let ident = idms_prox_write
54            .validate_client_auth_info_to_ident(client_auth_info, ct)
55            .map_err(|e| {
56                error!(err = ?e, "Invalid identity");
57                e
58            })?;
59
60        let target_uuid = idms_prox_write
61            .qs_write
62            .name_to_uuid(uuid_or_name)
63            .map_err(|e| {
64                error!(err = ?e, "Error resolving id to target");
65                e
66            })?;
67
68        let mdf = match ModifyEvent::from_parts(
69            ident,
70            target_uuid,
71            proto_ml,
72            filter,
73            &mut idms_prox_write.qs_write,
74        ) {
75            Ok(m) => m,
76            Err(e) => {
77                error!(err=?e, "Failed to begin modify during modify_from_parts");
78                return Err(e);
79            }
80        };
81
82        trace!(?mdf, "Begin modify event");
83
84        idms_prox_write
85            .qs_write
86            .modify(&mdf)
87            .and_then(|_| idms_prox_write.commit().map(|_| ()))
88    }
89
90    #[instrument(level = "debug", skip_all)]
91    async fn modify_from_internal_parts(
92        &self,
93        client_auth_info: ClientAuthInfo,
94        uuid_or_name: &str,
95        ml: &ModifyList<ModifyInvalid>,
96        filter: Filter<FilterInvalid>,
97    ) -> Result<(), OperationError> {
98        let ct = duration_from_epoch_now();
99        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
100
101        let ident = idms_prox_write
102            .validate_client_auth_info_to_ident(client_auth_info, ct)
103            .map_err(|e| {
104                error!(err = ?e, "Invalid identity");
105                e
106            })?;
107
108        let target_uuid = idms_prox_write
109            .qs_write
110            .name_to_uuid(uuid_or_name)
111            .inspect_err(|err| {
112                error!(?err, "Error resolving id to target");
113            })?;
114
115        let f_uuid = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(target_uuid)));
116        // Add any supplemental conditions we have.
117        let joined_filter = Filter::join_parts_and(f_uuid, filter);
118
119        let mdf = match ModifyEvent::from_internal_parts(
120            ident,
121            ml,
122            &joined_filter,
123            &idms_prox_write.qs_write,
124        ) {
125            Ok(m) => m,
126            Err(e) => {
127                error!(err = ?e, "Failed to begin modify during modify_from_internal_parts");
128                return Err(e);
129            }
130        };
131
132        trace!(?mdf, "Begin modify event");
133
134        idms_prox_write
135            .qs_write
136            .modify(&mdf)
137            .and_then(|_| idms_prox_write.commit().map(|_| ()))
138    }
139
140    #[instrument(
141        level = "info",
142        skip_all,
143        fields(uuid = ?eventid)
144    )]
145    pub async fn handle_create(
146        &self,
147        client_auth_info: ClientAuthInfo,
148        req: CreateRequest,
149        eventid: Uuid,
150    ) -> Result<(), OperationError> {
151        let ct = duration_from_epoch_now();
152        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
153
154        let ident = idms_prox_write
155            .validate_client_auth_info_to_ident(client_auth_info, ct)
156            .map_err(|e| {
157                error!(err = ?e, "Invalid identity");
158                e
159            })?;
160
161        let crt = match CreateEvent::from_message(ident, &req, &mut idms_prox_write.qs_write) {
162            Ok(c) => c,
163            Err(e) => {
164                admin_warn!(err = ?e, "Failed to begin create");
165                return Err(e);
166            }
167        };
168
169        trace!(?crt, "Begin create event");
170
171        idms_prox_write
172            .qs_write
173            .create(&crt)
174            .and_then(|_| idms_prox_write.commit())
175    }
176
177    #[instrument(
178        level = "info",
179        skip_all,
180        fields(uuid = ?eventid)
181    )]
182    pub async fn handle_modify(
183        &self,
184        client_auth_info: ClientAuthInfo,
185        req: ModifyRequest,
186        eventid: Uuid,
187    ) -> Result<(), OperationError> {
188        let ct = duration_from_epoch_now();
189        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
190        let ident = idms_prox_write
191            .validate_client_auth_info_to_ident(client_auth_info, ct)
192            .map_err(|e| {
193                error!(err = ?e, "Invalid identity");
194                e
195            })?;
196
197        let mdf = match ModifyEvent::from_message(ident, &req, &mut idms_prox_write.qs_write) {
198            Ok(m) => m,
199            Err(e) => {
200                error!(err = ?e, "Failed to begin modify during handle_modify");
201                return Err(e);
202            }
203        };
204
205        trace!(?mdf, "Begin modify event");
206
207        idms_prox_write
208            .qs_write
209            .modify(&mdf)
210            .and_then(|_| idms_prox_write.commit())
211    }
212
213    #[instrument(
214        level = "info",
215        skip_all,
216        fields(uuid = ?eventid)
217    )]
218    pub async fn handle_delete(
219        &self,
220        client_auth_info: ClientAuthInfo,
221        req: DeleteRequest,
222        eventid: Uuid,
223    ) -> Result<(), OperationError> {
224        let ct = duration_from_epoch_now();
225        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
226        let ident = idms_prox_write
227            .validate_client_auth_info_to_ident(client_auth_info, ct)
228            .map_err(|e| {
229                error!(err = ?e, "Invalid identity");
230                e
231            })?;
232        let del = match DeleteEvent::from_message(ident, &req, &mut idms_prox_write.qs_write) {
233            Ok(d) => d,
234            Err(e) => {
235                error!(err = ?e, "Failed to begin delete");
236                return Err(e);
237            }
238        };
239
240        trace!(?del, "Begin delete event");
241
242        idms_prox_write
243            .qs_write
244            .delete(&del)
245            .and_then(|_| idms_prox_write.commit())
246    }
247
248    #[instrument(
249        level = "info",
250        skip_all,
251        fields(uuid = ?eventid)
252    )]
253    pub async fn handle_internalpatch(
254        &self,
255        client_auth_info: ClientAuthInfo,
256        filter: Filter<FilterInvalid>,
257        update: ProtoEntry,
258        eventid: Uuid,
259    ) -> Result<(), OperationError> {
260        // Given a protoEntry, turn this into a modification set.
261        let ct = duration_from_epoch_now();
262        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
263        let ident = idms_prox_write
264            .validate_client_auth_info_to_ident(client_auth_info, ct)
265            .map_err(|e| {
266                error!(err = ?e, "Invalid identity");
267                e
268            })?;
269
270        // Transform the ProtoEntry to a Modlist
271        let modlist =
272            ModifyList::from_patch(&update, &mut idms_prox_write.qs_write).map_err(|e| {
273                error!(err = ?e, "Invalid Patch Request");
274                e
275            })?;
276
277        let mdf =
278            ModifyEvent::from_internal_parts(ident, &modlist, &filter, &idms_prox_write.qs_write)
279                .map_err(|e| {
280                error!(err = ?e, "Failed to begin modify during handle_internalpatch");
281                e
282            })?;
283
284        trace!(?mdf, "Begin modify event");
285
286        idms_prox_write
287            .qs_write
288            .modify(&mdf)
289            .and_then(|_| idms_prox_write.commit())
290    }
291
292    #[instrument(
293        level = "info",
294        skip_all,
295        fields(uuid = ?eventid)
296    )]
297    pub async fn handle_internaldelete(
298        &self,
299        client_auth_info: ClientAuthInfo,
300        filter: Filter<FilterInvalid>,
301        eventid: Uuid,
302    ) -> Result<(), OperationError> {
303        let ct = duration_from_epoch_now();
304        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
305        let ident = idms_prox_write
306            .validate_client_auth_info_to_ident(client_auth_info, ct)
307            .map_err(|e| {
308                error!(err = ?e, "Invalid identity");
309                e
310            })?;
311        let del = match DeleteEvent::from_parts(ident, &filter, &mut idms_prox_write.qs_write) {
312            Ok(d) => d,
313            Err(e) => {
314                error!(err = ?e, "Failed to begin delete");
315                return Err(e);
316            }
317        };
318
319        trace!(?del, "Begin delete event");
320
321        idms_prox_write
322            .qs_write
323            .delete(&del)
324            .and_then(|_| idms_prox_write.commit().map(|_| ()))
325    }
326
327    #[instrument(
328        level = "info",
329        skip_all,
330        fields(uuid = ?eventid)
331    )]
332    pub async fn handle_reviverecycled(
333        &self,
334        client_auth_info: ClientAuthInfo,
335        filter: Filter<FilterInvalid>,
336        eventid: Uuid,
337    ) -> Result<(), OperationError> {
338        let ct = duration_from_epoch_now();
339        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
340        let ident = idms_prox_write
341            .validate_client_auth_info_to_ident(client_auth_info, ct)
342            .map_err(|e| {
343                error!(err = ?e, "Invalid identity");
344                e
345            })?;
346        let rev = match ReviveRecycledEvent::from_parts(ident, &filter, &idms_prox_write.qs_write) {
347            Ok(r) => r,
348            Err(e) => {
349                error!(err = ?e, "Failed to begin revive");
350                return Err(e);
351            }
352        };
353
354        trace!(?rev, "Begin revive event");
355
356        idms_prox_write
357            .qs_write
358            .revive_recycled(&rev)
359            .and_then(|_| idms_prox_write.commit().map(|_| ()))
360    }
361
362    #[instrument(
363        level = "info",
364        skip_all,
365        fields(uuid = ?eventid)
366    )]
367    pub async fn handle_service_account_credential_generate(
368        &self,
369        client_auth_info: ClientAuthInfo,
370        uuid_or_name: String,
371        eventid: Uuid,
372    ) -> Result<String, OperationError> {
373        let ct = duration_from_epoch_now();
374        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
375        let ident = idms_prox_write
376            .validate_client_auth_info_to_ident(client_auth_info, ct)
377            .map_err(|e| {
378                error!(err = ?e, "Invalid identity");
379                e
380            })?;
381
382        // given the uuid_or_name, determine the target uuid.
383        // We can either do this by trying to parse the name or by creating a filter
384        // to find the entry - there are risks to both TBH ... especially when the uuid
385        // is also an entries name, but that they aren't the same entry.
386
387        let target_uuid = idms_prox_write
388            .qs_write
389            .name_to_uuid(uuid_or_name.as_str())
390            .map_err(|e| {
391                error!(err = ?e, "Error resolving id to target");
392                e
393            })?;
394
395        let gpe = GeneratePasswordEvent::from_parts(ident, target_uuid).map_err(|e| {
396            error!(
397                err = ?e,
398                "Failed to begin handle_service_account_credential_generate",
399            );
400            e
401        })?;
402        idms_prox_write
403            .generate_service_account_password(&gpe)
404            .and_then(|r| idms_prox_write.commit().map(|_| r))
405    }
406
407    #[instrument(
408        level = "info",
409        skip_all,
410        fields(uuid = ?eventid)
411    )]
412    pub async fn handle_service_account_api_token_generate(
413        &self,
414        client_auth_info: ClientAuthInfo,
415        uuid_or_name: String,
416        label: String,
417        expiry: Option<OffsetDateTime>,
418        read_write: bool,
419        eventid: Uuid,
420    ) -> Result<String, OperationError> {
421        let ct = duration_from_epoch_now();
422        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
423        let ident = idms_prox_write
424            .validate_client_auth_info_to_ident(client_auth_info, ct)
425            .map_err(|e| {
426                error!(err = ?e, "Invalid identity");
427                e
428            })?;
429
430        let target = idms_prox_write
431            .qs_write
432            .name_to_uuid(uuid_or_name.as_str())
433            .map_err(|e| {
434                error!(err = ?e, "Error resolving id to target");
435                e
436            })?;
437
438        let gte = GenerateApiTokenEvent {
439            ident,
440            target,
441            label,
442            expiry,
443            read_write,
444        };
445
446        idms_prox_write
447            .service_account_generate_api_token(&gte, ct)
448            .and_then(|r| idms_prox_write.commit().map(|_| r))
449            .map(|token| token.to_string())
450    }
451
452    #[instrument(
453        level = "info",
454        skip_all,
455        fields(uuid = ?eventid)
456    )]
457    pub async fn handle_service_account_api_token_destroy(
458        &self,
459        client_auth_info: ClientAuthInfo,
460        uuid_or_name: String,
461        token_id: Uuid,
462        eventid: Uuid,
463    ) -> Result<(), OperationError> {
464        let ct = duration_from_epoch_now();
465        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
466        let ident = idms_prox_write
467            .validate_client_auth_info_to_ident(client_auth_info, ct)
468            .map_err(|e| {
469                error!(err = ?e, "Invalid identity");
470                e
471            })?;
472
473        let target = idms_prox_write
474            .qs_write
475            .name_to_uuid(uuid_or_name.as_str())
476            .map_err(|e| {
477                error!(err = ?e, "Error resolving id to target");
478                e
479            })?;
480
481        let dte = DestroyApiTokenEvent {
482            ident,
483            target,
484            token_id,
485        };
486
487        idms_prox_write
488            .service_account_destroy_api_token(&dte)
489            .and_then(|r| idms_prox_write.commit().map(|_| r))
490    }
491
492    #[instrument(
493        level = "info",
494        skip_all,
495        fields(uuid = ?eventid)
496    )]
497    pub async fn handle_account_user_auth_token_destroy(
498        &self,
499        client_auth_info: ClientAuthInfo,
500        uuid_or_name: String,
501        token_id: Uuid,
502        eventid: Uuid,
503    ) -> Result<(), OperationError> {
504        let ct = duration_from_epoch_now();
505        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
506        let ident = idms_prox_write
507            .validate_client_auth_info_to_ident(client_auth_info, ct)
508            .map_err(|e| {
509                error!(err = ?e, "Invalid identity");
510                e
511            })?;
512
513        let target = idms_prox_write
514            .qs_write
515            .name_to_uuid(uuid_or_name.as_str())
516            .map_err(|e| {
517                error!(err = ?e, "Error resolving id to target");
518                e
519            })?;
520
521        let dte = DestroySessionTokenEvent {
522            ident,
523            target,
524            token_id,
525        };
526
527        idms_prox_write
528            .account_destroy_session_token(&dte)
529            .and_then(|r| idms_prox_write.commit().map(|_| r))
530    }
531
532    #[instrument(
533        level = "info",
534        skip_all,
535        fields(uuid = ?eventid)
536    )]
537    pub async fn handle_logout(
538        &self,
539        client_auth_info: ClientAuthInfo,
540        eventid: Uuid,
541    ) -> Result<(), OperationError> {
542        let ct = duration_from_epoch_now();
543        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
544
545        // We specifically need a uat here to assess the auth type!
546        let validate_result =
547            idms_prox_write.validate_client_auth_info_to_ident(client_auth_info, ct);
548
549        let ident = match validate_result {
550            Ok(ident) => ident,
551            Err(OperationError::SessionExpired) | Err(OperationError::NotAuthenticated) => {
552                return Ok(())
553            }
554            Err(err) => {
555                error!(?err, "Invalid identity");
556                return Err(err);
557            }
558        };
559
560        if !ident.can_logout() {
561            info!("Ignoring request to logout session - these sessions are not recorded");
562            return Ok(());
563        };
564
565        let target = ident.get_uuid().ok_or_else(|| {
566            error!("Invalid identity - no uuid present");
567            OperationError::InvalidState
568        })?;
569
570        let token_id = ident.get_session_id();
571
572        let dte = DestroySessionTokenEvent {
573            ident,
574            target,
575            token_id,
576        };
577
578        idms_prox_write
579            .account_destroy_session_token(&dte)
580            .and_then(|r| idms_prox_write.commit().map(|_| r))
581    }
582
583    #[instrument(
584        level = "info",
585        skip_all,
586        fields(uuid = ?eventid)
587    )]
588    pub async fn handle_idmcredentialupdate(
589        &self,
590        client_auth_info: ClientAuthInfo,
591        uuid_or_name: String,
592        eventid: Uuid,
593    ) -> Result<(CUSessionToken, CUStatus), OperationError> {
594        let ct = duration_from_epoch_now();
595        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
596        let ident = idms_prox_write
597            .validate_client_auth_info_to_ident(client_auth_info, ct)
598            .map_err(|e| {
599                error!(err = ?e, "Invalid identity");
600                e
601            })?;
602
603        let target_uuid = idms_prox_write
604            .qs_write
605            .name_to_uuid(uuid_or_name.as_str())
606            .map_err(|e| {
607                error!(err = ?e, "Error resolving id to target");
608                e
609            })?;
610
611        idms_prox_write
612            .init_credential_update(&InitCredentialUpdateEvent::new(ident, target_uuid), ct)
613            .and_then(|tok| idms_prox_write.commit().map(|_| tok))
614            .map_err(|e| {
615                error!(
616                    err = ?e,
617                    "Failed to begin init_credential_update",
618                );
619                e
620            })
621            .map(|(tok, sta)| {
622                (
623                    CUSessionToken {
624                        token: tok.token_enc.to_string(),
625                    },
626                    sta.into(),
627                )
628            })
629    }
630
631    #[instrument(
632        level = "info",
633        skip_all,
634        fields(uuid = ?eventid),
635    )]
636    pub async fn handle_idmcredentialupdateintent(
637        &self,
638        client_auth_info: ClientAuthInfo,
639        uuid_or_name: String,
640        ttl: Option<Duration>,
641        eventid: Uuid,
642    ) -> Result<CUIntentToken, OperationError> {
643        let ct = duration_from_epoch_now();
644        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
645        let ident = idms_prox_write
646            .validate_client_auth_info_to_ident(client_auth_info, ct)
647            .map_err(|e| {
648                error!(err = ?e, "Invalid identity");
649                e
650            })?;
651
652        let target_uuid = idms_prox_write
653            .qs_write
654            .name_to_uuid(uuid_or_name.as_str())
655            .map_err(|e| {
656                error!(err = ?e, "Error resolving id to target");
657                e
658            })?;
659
660        idms_prox_write
661            .init_credential_update_intent(
662                &InitCredentialUpdateIntentEvent::new(ident, target_uuid, ttl),
663                ct,
664            )
665            .and_then(|tok| idms_prox_write.commit().map(|_| tok))
666            .map_err(|e| {
667                error!(
668                    err = ?e,
669                    "Failed to begin init_credential_update_intent",
670                );
671                e
672            })
673            .map(|tok| CUIntentToken {
674                token: tok.intent_id,
675                expiry_time: tok.expiry_time,
676            })
677    }
678
679    #[instrument(
680        level = "info",
681        skip_all,
682        fields(uuid = ?eventid)
683    )]
684    pub async fn handle_idmcredentialexchangeintent(
685        &self,
686        intent_id: String,
687        eventid: Uuid,
688    ) -> Result<(CUSessionToken, CUStatus), OperationError> {
689        let ct = duration_from_epoch_now();
690        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
691        let intent_token = CredentialUpdateIntentTokenExchange { intent_id };
692        // TODO: this is throwing a 500 error when a session is already in use, that seems bad?
693        idms_prox_write
694            .exchange_intent_credential_update(intent_token, ct)
695            .and_then(|tok| idms_prox_write.commit().map(|_| tok))
696            .map_err(|e| {
697                error!(
698                    err = ?e,
699                    "Failed to begin exchange_intent_credential_update",
700                );
701                e
702            })
703            .map(|(tok, sta)| {
704                (
705                    CUSessionToken {
706                        token: tok.token_enc.to_string(),
707                    },
708                    sta.into(),
709                )
710            })
711    }
712
713    #[instrument(
714        level = "info",
715        skip_all,
716        fields(uuid = ?eventid)
717    )]
718    pub async fn handle_idmcredentialupdatecommit(
719        &self,
720        session_token: CUSessionToken,
721        eventid: Uuid,
722    ) -> Result<(), OperationError> {
723        let session_token = JweCompact::from_str(session_token.token.as_str())
724            .map(|token_enc| CredentialUpdateSessionToken { token_enc })
725            .map_err(|err| {
726                error!(?err, "malformed token");
727                OperationError::InvalidRequestState
728            })?;
729
730        let ct = duration_from_epoch_now();
731        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
732
733        idms_prox_write
734            .commit_credential_update(&session_token, ct)
735            .and_then(|tok| idms_prox_write.commit().map(|_| tok))
736            .map_err(|e| {
737                error!(
738                    err = ?e,
739                    "Failed to begin commit_credential_update",
740                );
741                e
742            })
743    }
744
745    #[instrument(
746        level = "info",
747        skip_all,
748        fields(uuid = ?eventid)
749    )]
750    pub async fn handle_idmcredentialupdatecancel(
751        &self,
752        session_token: CUSessionToken,
753        eventid: Uuid,
754    ) -> Result<(), OperationError> {
755        let session_token = JweCompact::from_str(session_token.token.as_str())
756            .map(|token_enc| CredentialUpdateSessionToken { token_enc })
757            .map_err(|err| {
758                error!(?err, "malformed token");
759                OperationError::InvalidRequestState
760            })?;
761
762        let ct = duration_from_epoch_now();
763        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
764
765        idms_prox_write
766            .cancel_credential_update(&session_token, ct)
767            .and_then(|tok| idms_prox_write.commit().map(|_| tok))
768            .map_err(|e| {
769                error!(
770                    err = ?e,
771                    "Failed to begin commit_credential_cancel",
772                );
773                e
774            })
775    }
776
777    #[instrument(
778        level = "info",
779        skip_all,
780        fields(uuid = ?eventid)
781    )]
782    pub async fn handle_service_account_into_person(
783        &self,
784        client_auth_info: ClientAuthInfo,
785        uuid_or_name: String,
786        eventid: Uuid,
787    ) -> Result<(), OperationError> {
788        let ct = duration_from_epoch_now();
789        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
790        let ident = idms_prox_write
791            .validate_client_auth_info_to_ident(client_auth_info, ct)
792            .map_err(|e| {
793                error!(err = ?e, "Invalid identity");
794                e
795            })?;
796        let target_uuid = idms_prox_write
797            .qs_write
798            .name_to_uuid(uuid_or_name.as_str())
799            .map_err(|e| {
800                error!(err = ?e, "Error resolving id to target");
801                e
802            })?;
803
804        idms_prox_write
805            .service_account_into_person(&ident, target_uuid)
806            .and_then(|_| idms_prox_write.commit())
807    }
808
809    #[instrument(
810        level = "info",
811        skip_all,
812        fields(uuid = ?eventid)
813    )]
814    pub async fn handle_regenerateradius(
815        &self,
816        client_auth_info: ClientAuthInfo,
817        uuid_or_name: String,
818        eventid: Uuid,
819    ) -> Result<String, OperationError> {
820        let ct = duration_from_epoch_now();
821        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
822        let ident = idms_prox_write
823            .validate_client_auth_info_to_ident(client_auth_info, ct)
824            .map_err(|e| {
825                error!(err = ?e, "Invalid identity");
826                e
827            })?;
828
829        let target_uuid = idms_prox_write
830            .qs_write
831            .name_to_uuid(uuid_or_name.as_str())
832            .map_err(|e| {
833                error!(err = ?e, "Error resolving id to target");
834                e
835            })?;
836
837        let rrse = RegenerateRadiusSecretEvent::from_parts(
838            // &idms_prox_write.qs_write,
839            ident,
840            target_uuid,
841        )
842        .map_err(|e| {
843            error!(
844                err = ?e,
845                "Failed to begin idm_account_regenerate_radius",
846            );
847            e
848        })?;
849
850        idms_prox_write
851            .regenerate_radius_secret(&rrse)
852            .and_then(|r| idms_prox_write.commit().map(|_| r))
853    }
854
855    #[instrument(
856        level = "info",
857        skip_all,
858        fields(uuid = ?eventid)
859    )]
860    pub async fn handle_purgeattribute(
861        &self,
862        client_auth_info: ClientAuthInfo,
863        uuid_or_name: String,
864        attr: String,
865        filter: Filter<FilterInvalid>,
866        eventid: Uuid,
867    ) -> Result<(), OperationError> {
868        let ct = duration_from_epoch_now();
869        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
870        let ident = idms_prox_write
871            .validate_client_auth_info_to_ident(client_auth_info, ct)
872            .map_err(|e| {
873                error!(err = ?e, "Invalid identity");
874                e
875            })?;
876        let target_uuid = idms_prox_write
877            .qs_write
878            .name_to_uuid(uuid_or_name.as_str())
879            .map_err(|e| {
880                error!(err = ?e, "Error resolving id to target");
881                e
882            })?;
883
884        let target_attr = Attribute::from(attr.as_str());
885        let mdf = match ModifyEvent::from_target_uuid_attr_purge(
886            ident,
887            target_uuid,
888            target_attr,
889            filter,
890            &idms_prox_write.qs_write,
891        ) {
892            Ok(m) => m,
893            Err(e) => {
894                error!(err = ?e, "Failed to begin modify during purge attribute");
895                return Err(e);
896            }
897        };
898
899        trace!(?mdf, "Begin modify event");
900
901        idms_prox_write
902            .qs_write
903            .modify(&mdf)
904            .and_then(|_| idms_prox_write.commit().map(|_| ()))
905    }
906
907    #[instrument(
908        level = "info",
909        skip_all,
910        fields(uuid = ?eventid)
911    )]
912    pub async fn handle_removeattributevalues(
913        &self,
914        client_auth_info: ClientAuthInfo,
915        uuid_or_name: String,
916        attr: String,
917        values: Vec<String>,
918        filter: Filter<FilterInvalid>,
919        eventid: Uuid,
920    ) -> Result<(), OperationError> {
921        let ct = duration_from_epoch_now();
922        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
923        let ident = idms_prox_write
924            .validate_client_auth_info_to_ident(client_auth_info, ct)
925            .map_err(|e| {
926                error!(err = ?e, "Invalid identity");
927                e
928            })?;
929        let target_uuid = idms_prox_write
930            .qs_write
931            .name_to_uuid(uuid_or_name.as_str())
932            .map_err(|e| {
933                error!(err = ?e, "Error resolving id to target");
934                e
935            })?;
936
937        let proto_ml = ProtoModifyList::new_list(
938            values
939                .into_iter()
940                .map(|v| ProtoModify::Removed(attr.clone(), v))
941                .collect(),
942        );
943
944        let mdf = match ModifyEvent::from_parts(
945            ident,
946            target_uuid,
947            &proto_ml,
948            filter,
949            &mut idms_prox_write.qs_write,
950        ) {
951            Ok(m) => m,
952            Err(e) => {
953                error!(err = ?e, "Failed to begin modify");
954                return Err(e);
955            }
956        };
957
958        trace!(?mdf, "Begin modify event");
959
960        idms_prox_write
961            .qs_write
962            .modify(&mdf)
963            .and_then(|_| idms_prox_write.commit().map(|_| ()))
964    }
965
966    #[instrument(
967        level = "info",
968        name = "append_attribute",
969        skip_all,
970        fields(uuid = ?eventid)
971    )]
972    pub async fn handle_appendattribute(
973        &self,
974        client_auth_info: ClientAuthInfo,
975        uuid_or_name: String,
976        attr: String,
977        values: Vec<String>,
978        filter: Filter<FilterInvalid>,
979        eventid: Uuid,
980    ) -> Result<(), OperationError> {
981        // We need to turn these into proto modlists so they can be converted
982        // and validated.
983        let proto_ml = ProtoModifyList::new_list(
984            values
985                .into_iter()
986                .map(|v| ProtoModify::Present(attr.clone(), v))
987                .collect(),
988        );
989        self.modify_from_parts(client_auth_info, &uuid_or_name, &proto_ml, filter)
990            .await
991    }
992
993    #[instrument(
994        level = "info",
995        name = "set_attribute",
996        skip_all,
997        fields(uuid = ?eventid)
998    )]
999    pub async fn handle_setattribute(
1000        &self,
1001        client_auth_info: ClientAuthInfo,
1002        uuid_or_name: String,
1003        attr: String,
1004        values: Vec<String>,
1005        filter: Filter<FilterInvalid>,
1006        eventid: Uuid,
1007    ) -> Result<(), OperationError> {
1008        // We need to turn these into proto modlists so they can be converted
1009        // and validated.
1010        let proto_ml = ProtoModifyList::new_list(
1011            std::iter::once(ProtoModify::Purged(attr.clone()))
1012                .chain(
1013                    values
1014                        .into_iter()
1015                        .map(|v| ProtoModify::Present(attr.clone(), v)),
1016                )
1017                .collect(),
1018        );
1019        self.modify_from_parts(client_auth_info, &uuid_or_name, &proto_ml, filter)
1020            .await
1021    }
1022
1023    #[instrument(
1024        level = "info",
1025        name = "ssh_key_create",
1026        skip_all,
1027        fields(uuid = ?eventid)
1028    )]
1029    pub async fn handle_sshkeycreate(
1030        &self,
1031        client_auth_info: ClientAuthInfo,
1032        uuid_or_name: String,
1033        tag: &str,
1034        key: &str,
1035        filter: Filter<FilterInvalid>,
1036        eventid: Uuid,
1037    ) -> Result<(), OperationError> {
1038        let v_sk = Value::new_sshkey_str(tag, key)?;
1039
1040        // Because this is from internal, we can generate a real modlist, rather
1041        // than relying on the proto ones.
1042        let ml = ModifyList::new_append(Attribute::SshPublicKey, v_sk);
1043
1044        self.modify_from_internal_parts(client_auth_info, &uuid_or_name, &ml, filter)
1045            .await
1046    }
1047
1048    #[instrument(
1049        level = "info",
1050        name = "idm_account_unix_extend",
1051        skip_all,
1052        fields(uuid = ?eventid)
1053    )]
1054    pub async fn handle_idmaccountunixextend(
1055        &self,
1056        client_auth_info: ClientAuthInfo,
1057        uuid_or_name: String,
1058        ux: AccountUnixExtend,
1059        eventid: Uuid,
1060    ) -> Result<(), OperationError> {
1061        let AccountUnixExtend { gidnumber, shell } = ux;
1062        // The filter_map here means we only create the mods if the gidnumber or shell are set
1063        // in the actual request.
1064        let mods: Vec<_> = iter::once(Some(Modify::Present(
1065            Attribute::Class,
1066            EntryClass::PosixAccount.into(),
1067        )))
1068        .chain(iter::once(
1069            gidnumber
1070                .as_ref()
1071                .map(|_| Modify::Purged(Attribute::GidNumber)),
1072        ))
1073        .chain(iter::once(gidnumber.map(|n| {
1074            Modify::Present(Attribute::GidNumber, Value::new_uint32(n))
1075        })))
1076        .chain(iter::once(
1077            shell
1078                .as_ref()
1079                .map(|_| Modify::Purged(Attribute::LoginShell)),
1080        ))
1081        .chain(iter::once(shell.map(|s| {
1082            Modify::Present(Attribute::LoginShell, Value::new_iutf8(s.as_str()))
1083        })))
1084        .flatten()
1085        .collect();
1086
1087        let ml = ModifyList::new_list(mods);
1088
1089        let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1090
1091        self.modify_from_internal_parts(client_auth_info, &uuid_or_name, &ml, filter)
1092            .await
1093    }
1094
1095    #[instrument(
1096        level = "info",
1097        name = "idm_group_unix_extend",
1098        skip_all,
1099        fields(uuid = ?eventid)
1100    )]
1101    pub async fn handle_idmgroupunixextend(
1102        &self,
1103        client_auth_info: ClientAuthInfo,
1104        uuid_or_name: String,
1105        gx: GroupUnixExtend,
1106        eventid: Uuid,
1107    ) -> Result<(), OperationError> {
1108        // The if let Some here means we only create the mods if the gidnumber is set
1109        // in the actual request.
1110
1111        let gidnumber_mods = if let Some(gid) = gx.gidnumber {
1112            [
1113                Some(Modify::Purged(Attribute::GidNumber)),
1114                Some(Modify::Present(
1115                    Attribute::GidNumber,
1116                    Value::new_uint32(gid),
1117                )),
1118            ]
1119        } else {
1120            [None, None]
1121        };
1122        let mods: Vec<_> = iter::once(Some(Modify::Present(
1123            Attribute::Class,
1124            EntryClass::PosixGroup.into(),
1125        )))
1126        .chain(gidnumber_mods)
1127        .flatten()
1128        .collect();
1129
1130        let ml = ModifyList::new_list(mods);
1131
1132        let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Group.into()));
1133
1134        self.modify_from_internal_parts(client_auth_info, &uuid_or_name, &ml, filter)
1135            .await
1136    }
1137
1138    #[instrument(
1139        level = "info",
1140        skip_all,
1141        fields(uuid = ?eventid)
1142    )]
1143    pub async fn handle_idmaccountunixsetcred(
1144        &self,
1145        client_auth_info: ClientAuthInfo,
1146        uuid_or_name: String,
1147        cred: String,
1148        eventid: Uuid,
1149    ) -> Result<(), OperationError> {
1150        let ct = duration_from_epoch_now();
1151        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
1152        let ident = idms_prox_write
1153            .validate_client_auth_info_to_ident(client_auth_info, ct)
1154            .map_err(|e| {
1155                error!(err = ?e, "Invalid identity");
1156                e
1157            })?;
1158
1159        let target_uuid = Uuid::parse_str(uuid_or_name.as_str()).or_else(|_| {
1160            idms_prox_write
1161                .qs_write
1162                .name_to_uuid(uuid_or_name.as_str())
1163                .inspect_err(|err| {
1164                    info!(?err, "Error resolving as gidnumber continuing ...");
1165                })
1166        })?;
1167
1168        let upce = UnixPasswordChangeEvent::from_parts(
1169            // &idms_prox_write.qs_write,
1170            ident,
1171            target_uuid,
1172            cred,
1173        )
1174        .map_err(|e| {
1175            error!(err = ?e, "Failed to begin UnixPasswordChangeEvent");
1176            e
1177        })?;
1178        idms_prox_write
1179            .set_unix_account_password(&upce)
1180            .and_then(|_| idms_prox_write.commit())
1181            .map(|_| ())
1182    }
1183
1184    #[instrument(level = "debug", skip_all)]
1185    pub async fn handle_image_update(
1186        &self,
1187        client_auth_info: ClientAuthInfo,
1188        request_filter: Filter<FilterInvalid>,
1189        image: Option<ImageValue>,
1190    ) -> Result<(), OperationError> {
1191        let ct = duration_from_epoch_now();
1192        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
1193
1194        let ident = idms_prox_write
1195            .validate_client_auth_info_to_ident(client_auth_info, ct)
1196            .inspect_err(|err| {
1197                error!(?err, "Invalid identity in handle_image_update");
1198            })?;
1199
1200        let modlist = if let Some(image) = image {
1201            ModifyList::new_purge_and_set(Attribute::Image, Value::Image(image))
1202        } else {
1203            ModifyList::new_purge(Attribute::Image)
1204        };
1205
1206        let mdf = ModifyEvent::from_internal_parts(
1207            ident,
1208            &modlist,
1209            &request_filter,
1210            &idms_prox_write.qs_write,
1211        )
1212        .inspect_err(|err| {
1213            error!(?err, "Failed to begin modify during handle_image_update");
1214        })?;
1215
1216        idms_prox_write
1217            .qs_write
1218            .modify(&mdf)
1219            .and_then(|_| idms_prox_write.commit().map(|_| ()))
1220    }
1221
1222    #[instrument(
1223        level = "info",
1224        skip_all,
1225        fields(uuid = ?eventid)
1226    )]
1227    pub async fn handle_oauth2_scopemap_update(
1228        &self,
1229        client_auth_info: ClientAuthInfo,
1230        group: String,
1231        scopes: Vec<String>,
1232        filter: Filter<FilterInvalid>,
1233        eventid: Uuid,
1234    ) -> Result<(), OperationError> {
1235        // Because this is from internal, we can generate a real modlist, rather
1236        // than relying on the proto ones.
1237        let ct = duration_from_epoch_now();
1238        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
1239
1240        let ident = idms_prox_write
1241            .validate_client_auth_info_to_ident(client_auth_info, ct)
1242            .map_err(|e| {
1243                error!(err = ?e, "Invalid identity");
1244                e
1245            })?;
1246
1247        let group_uuid = idms_prox_write
1248            .qs_write
1249            .name_to_uuid(group.as_str())
1250            .map_err(|e| {
1251                error!(err = ?e, "Error resolving group name to target");
1252                e
1253            })?;
1254
1255        let ml = ModifyList::new_append(
1256            Attribute::OAuth2RsScopeMap,
1257            Value::new_oauthscopemap(group_uuid, scopes.into_iter().collect()).ok_or_else(
1258                || OperationError::InvalidAttribute("Invalid Oauth Scope Map syntax".to_string()),
1259            )?,
1260        );
1261
1262        let mdf = match ModifyEvent::from_internal_parts(
1263            ident,
1264            &ml,
1265            &filter,
1266            &idms_prox_write.qs_write,
1267        ) {
1268            Ok(m) => m,
1269            Err(e) => {
1270                error!(err = ?e, "Failed to begin modify");
1271                return Err(e);
1272            }
1273        };
1274
1275        trace!(?mdf, "Begin modify event");
1276
1277        idms_prox_write
1278            .qs_write
1279            .modify(&mdf)
1280            .and_then(|_| idms_prox_write.commit().map(|_| ()))
1281    }
1282
1283    #[instrument(
1284        level = "info",
1285        skip_all,
1286        fields(uuid = ?eventid)
1287    )]
1288    pub async fn handle_oauth2_scopemap_delete(
1289        &self,
1290        client_auth_info: ClientAuthInfo,
1291        group: String,
1292        filter: Filter<FilterInvalid>,
1293        eventid: Uuid,
1294    ) -> Result<(), OperationError> {
1295        let ct = duration_from_epoch_now();
1296        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
1297
1298        let ident = idms_prox_write
1299            .validate_client_auth_info_to_ident(client_auth_info, ct)
1300            .map_err(|e| {
1301                error!(err = ?e, "Invalid identity");
1302                e
1303            })?;
1304
1305        let group_uuid = idms_prox_write
1306            .qs_write
1307            .name_to_uuid(group.as_str())
1308            .map_err(|e| {
1309                error!(err = ?e, "Error resolving group name to target");
1310                e
1311            })?;
1312
1313        let ml =
1314            ModifyList::new_remove(Attribute::OAuth2RsScopeMap, PartialValue::Refer(group_uuid));
1315
1316        let mdf = match ModifyEvent::from_internal_parts(
1317            ident,
1318            &ml,
1319            &filter,
1320            &idms_prox_write.qs_write,
1321        ) {
1322            Ok(m) => m,
1323            Err(e) => {
1324                error!(err = ?e, "Failed to begin modify");
1325                return Err(e);
1326            }
1327        };
1328
1329        trace!(?mdf, "Begin modify event");
1330
1331        idms_prox_write
1332            .qs_write
1333            .modify(&mdf)
1334            .and_then(|_| idms_prox_write.commit().map(|_| ()))
1335    }
1336
1337    #[instrument(
1338        level = "info",
1339        skip_all,
1340        fields(uuid = ?eventid)
1341    )]
1342    pub async fn handle_oauth2_claimmap_update(
1343        &self,
1344        client_auth_info: ClientAuthInfo,
1345        claim_name: String,
1346        group: String,
1347        claims: Vec<String>,
1348        filter: Filter<FilterInvalid>,
1349        eventid: Uuid,
1350    ) -> Result<(), OperationError> {
1351        // Because this is from internal, we can generate a real modlist, rather
1352        // than relying on the proto ones.
1353        let ct = duration_from_epoch_now();
1354        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
1355
1356        let ident = idms_prox_write
1357            .validate_client_auth_info_to_ident(client_auth_info, ct)
1358            .map_err(|e| {
1359                error!(err = ?e, "Invalid identity");
1360                e
1361            })?;
1362
1363        let group_uuid = idms_prox_write
1364            .qs_write
1365            .name_to_uuid(group.as_str())
1366            .map_err(|e| {
1367                error!(err = ?e, "Error resolving group name to target");
1368                e
1369            })?;
1370
1371        let ml = ModifyList::new_append(
1372            Attribute::OAuth2RsClaimMap,
1373            Value::new_oauthclaimmap(claim_name, group_uuid, claims.into_iter().collect())
1374                .ok_or_else(|| {
1375                    OperationError::InvalidAttribute("Invalid Oauth Claim Map syntax".to_string())
1376                })?,
1377        );
1378
1379        let mdf = match ModifyEvent::from_internal_parts(
1380            ident,
1381            &ml,
1382            &filter,
1383            &idms_prox_write.qs_write,
1384        ) {
1385            Ok(m) => m,
1386            Err(e) => {
1387                error!(err = ?e, "Failed to begin modify");
1388                return Err(e);
1389            }
1390        };
1391
1392        trace!(?mdf, "Begin modify event");
1393
1394        idms_prox_write
1395            .qs_write
1396            .modify(&mdf)
1397            .and_then(|_| idms_prox_write.commit().map(|_| ()))
1398    }
1399
1400    #[instrument(
1401        level = "info",
1402        skip_all,
1403        fields(uuid = ?eventid)
1404    )]
1405    pub async fn handle_oauth2_claimmap_join_update(
1406        &self,
1407        client_auth_info: ClientAuthInfo,
1408        claim_name: String,
1409        join: ProtoOauth2ClaimMapJoin,
1410        filter: Filter<FilterInvalid>,
1411        eventid: Uuid,
1412    ) -> Result<(), OperationError> {
1413        // Because this is from internal, we can generate a real modlist, rather
1414        // than relying on the proto ones.
1415        let ct = duration_from_epoch_now();
1416        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
1417
1418        let ident = idms_prox_write
1419            .validate_client_auth_info_to_ident(client_auth_info, ct)
1420            .map_err(|e| {
1421                error!(err = ?e, "Invalid identity");
1422                e
1423            })?;
1424
1425        let join = match join {
1426            ProtoOauth2ClaimMapJoin::Csv => OauthClaimMapJoin::CommaSeparatedValue,
1427            ProtoOauth2ClaimMapJoin::Ssv => OauthClaimMapJoin::SpaceSeparatedValue,
1428            ProtoOauth2ClaimMapJoin::Array => OauthClaimMapJoin::JsonArray,
1429        };
1430
1431        let ml = ModifyList::new_append(
1432            Attribute::OAuth2RsClaimMap,
1433            Value::OauthClaimMap(claim_name, join),
1434        );
1435
1436        let mdf = match ModifyEvent::from_internal_parts(
1437            ident,
1438            &ml,
1439            &filter,
1440            &idms_prox_write.qs_write,
1441        ) {
1442            Ok(m) => m,
1443            Err(e) => {
1444                error!(err = ?e, "Failed to begin modify");
1445                return Err(e);
1446            }
1447        };
1448
1449        trace!(?mdf, "Begin modify event");
1450
1451        idms_prox_write
1452            .qs_write
1453            .modify(&mdf)
1454            .and_then(|_| idms_prox_write.commit().map(|_| ()))
1455    }
1456
1457    #[instrument(
1458        level = "info",
1459        skip_all,
1460        fields(uuid = ?eventid)
1461    )]
1462    pub async fn handle_oauth2_claimmap_delete(
1463        &self,
1464        client_auth_info: ClientAuthInfo,
1465        claim_name: String,
1466        group: String,
1467        filter: Filter<FilterInvalid>,
1468        eventid: Uuid,
1469    ) -> Result<(), OperationError> {
1470        let ct = duration_from_epoch_now();
1471        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
1472
1473        let ident = idms_prox_write
1474            .validate_client_auth_info_to_ident(client_auth_info, ct)
1475            .map_err(|e| {
1476                error!(err = ?e, "Invalid identity");
1477                e
1478            })?;
1479
1480        let group_uuid = idms_prox_write
1481            .qs_write
1482            .name_to_uuid(group.as_str())
1483            .map_err(|e| {
1484                error!(err = ?e, "Error resolving group name to target");
1485                e
1486            })?;
1487
1488        let ml = ModifyList::new_remove(
1489            Attribute::OAuth2RsClaimMap,
1490            PartialValue::OauthClaim(claim_name, group_uuid),
1491        );
1492
1493        let mdf = match ModifyEvent::from_internal_parts(
1494            ident,
1495            &ml,
1496            &filter,
1497            &idms_prox_write.qs_write,
1498        ) {
1499            Ok(m) => m,
1500            Err(e) => {
1501                error!(err = ?e, "Failed to begin modify");
1502                return Err(e);
1503            }
1504        };
1505
1506        trace!(?mdf, "Begin modify event");
1507
1508        idms_prox_write
1509            .qs_write
1510            .modify(&mdf)
1511            .and_then(|_| idms_prox_write.commit().map(|_| ()))
1512    }
1513
1514    #[instrument(
1515        level = "info",
1516        skip_all,
1517        fields(uuid = ?eventid)
1518    )]
1519    pub async fn handle_oauth2_sup_scopemap_update(
1520        &self,
1521        client_auth_info: ClientAuthInfo,
1522        group: String,
1523        scopes: Vec<String>,
1524        filter: Filter<FilterInvalid>,
1525        eventid: Uuid,
1526    ) -> Result<(), OperationError> {
1527        // Because this is from internal, we can generate a real modlist, rather
1528        // than relying on the proto ones.
1529        let ct = duration_from_epoch_now();
1530        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
1531
1532        let ident = idms_prox_write
1533            .validate_client_auth_info_to_ident(client_auth_info, ct)
1534            .map_err(|e| {
1535                error!(err = ?e, "Invalid identity");
1536                e
1537            })?;
1538
1539        let group_uuid = idms_prox_write
1540            .qs_write
1541            .name_to_uuid(group.as_str())
1542            .map_err(|e| {
1543                error!(err = ?e, "Error resolving group name to target");
1544                e
1545            })?;
1546
1547        let ml = ModifyList::new_append(
1548            Attribute::OAuth2RsSupScopeMap,
1549            Value::new_oauthscopemap(group_uuid, scopes.into_iter().collect()).ok_or_else(
1550                || OperationError::InvalidAttribute("Invalid Oauth Scope Map syntax".to_string()),
1551            )?,
1552        );
1553
1554        let mdf = match ModifyEvent::from_internal_parts(
1555            ident,
1556            &ml,
1557            &filter,
1558            &idms_prox_write.qs_write,
1559        ) {
1560            Ok(m) => m,
1561            Err(e) => {
1562                error!(err = ?e, "Failed to begin modify");
1563                return Err(e);
1564            }
1565        };
1566
1567        trace!(?mdf, "Begin modify event");
1568
1569        idms_prox_write
1570            .qs_write
1571            .modify(&mdf)
1572            .and_then(|_| idms_prox_write.commit().map(|_| ()))
1573    }
1574
1575    #[instrument(
1576        level = "info",
1577        skip_all,
1578        fields(uuid = ?eventid)
1579    )]
1580    pub async fn handle_oauth2_sup_scopemap_delete(
1581        &self,
1582        client_auth_info: ClientAuthInfo,
1583        group: String,
1584        filter: Filter<FilterInvalid>,
1585        eventid: Uuid,
1586    ) -> Result<(), OperationError> {
1587        let ct = duration_from_epoch_now();
1588        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
1589
1590        let ident = idms_prox_write
1591            .validate_client_auth_info_to_ident(client_auth_info, ct)
1592            .map_err(|e| {
1593                error!(err = ?e, "Invalid identity");
1594                e
1595            })?;
1596
1597        let group_uuid = idms_prox_write
1598            .qs_write
1599            .name_to_uuid(group.as_str())
1600            .map_err(|e| {
1601                error!(err = ?e, "Error resolving group name to target");
1602                e
1603            })?;
1604
1605        let ml = ModifyList::new_remove(
1606            Attribute::OAuth2RsSupScopeMap,
1607            PartialValue::Refer(group_uuid),
1608        );
1609
1610        let mdf = match ModifyEvent::from_internal_parts(
1611            ident,
1612            &ml,
1613            &filter,
1614            &idms_prox_write.qs_write,
1615        ) {
1616            Ok(m) => m,
1617            Err(e) => {
1618                error!(err = ?e, "Failed to begin modify");
1619                return Err(e);
1620            }
1621        };
1622
1623        trace!(?mdf, "Begin modify event");
1624
1625        idms_prox_write
1626            .qs_write
1627            .modify(&mdf)
1628            .and_then(|_| idms_prox_write.commit().map(|_| ()))
1629    }
1630
1631    #[instrument(
1632        level = "info",
1633        skip_all,
1634        fields(uuid = ?eventid)
1635    )]
1636    pub async fn handle_oauth2_authorise_permit(
1637        &self,
1638        client_auth_info: ClientAuthInfo,
1639        consent_req: String,
1640        eventid: Uuid,
1641    ) -> Result<AuthorisePermitSuccess, OperationError> {
1642        let ct = duration_from_epoch_now();
1643        let mut idms_prox_write = self.idms.proxy_write(ct).await?;
1644
1645        let ident = idms_prox_write
1646            .validate_client_auth_info_to_ident(client_auth_info, ct)
1647            .inspect_err(|err| {
1648                error!(?err, "Invalid identity");
1649            })?;
1650
1651        idms_prox_write
1652            .check_oauth2_authorise_permit(&ident, &consent_req, ct)
1653            .and_then(|r| idms_prox_write.commit().map(|()| r))
1654    }
1655
1656    #[instrument(
1657        level = "info",
1658        skip_all,
1659        fields(uuid = ?eventid)
1660    )]
1661    pub async fn handle_oauth2_token_exchange(
1662        &self,
1663        client_auth_info: ClientAuthInfo,
1664        token_req: AccessTokenRequest,
1665        eventid: Uuid,
1666    ) -> Result<AccessTokenResponse, Oauth2Error> {
1667        let ct = duration_from_epoch_now();
1668        let mut idms_prox_write = self
1669            .idms
1670            .proxy_write(ct)
1671            .await
1672            .map_err(Oauth2Error::ServerError)?;
1673        // Now we can send to the idm server for authorisation checking.
1674        let resp = idms_prox_write.check_oauth2_token_exchange(&client_auth_info, &token_req, ct);
1675
1676        match &resp {
1677            Err(Oauth2Error::InvalidGrant) | Ok(_) => {
1678                idms_prox_write.commit().map_err(Oauth2Error::ServerError)?;
1679            }
1680            _ => {}
1681        };
1682
1683        resp
1684    }
1685
1686    #[instrument(
1687        level = "info",
1688        skip_all,
1689        fields(uuid = ?eventid)
1690    )]
1691    pub async fn handle_oauth2_token_revoke(
1692        &self,
1693        client_auth_info: ClientAuthInfo,
1694        intr_req: TokenRevokeRequest,
1695        eventid: Uuid,
1696    ) -> Result<(), Oauth2Error> {
1697        let ct = duration_from_epoch_now();
1698        let mut idms_prox_write = self
1699            .idms
1700            .proxy_write(ct)
1701            .await
1702            .map_err(Oauth2Error::ServerError)?;
1703        idms_prox_write
1704            .oauth2_token_revoke(&client_auth_info, &intr_req, ct)
1705            .and_then(|()| idms_prox_write.commit().map_err(Oauth2Error::ServerError))
1706    }
1707
1708    #[cfg(feature = "dev-oauth2-device-flow")]
1709    pub async fn handle_oauth2_device_flow_start(
1710        &self,
1711        client_auth_info: ClientAuthInfo,
1712        client_id: &str,
1713        scope: &Option<BTreeSet<String>>,
1714        eventid: Uuid,
1715    ) -> Result<kanidm_proto::oauth2::DeviceAuthorizationResponse, Oauth2Error> {
1716        let ct = duration_from_epoch_now();
1717        let mut idms_prox_write = self
1718            .idms
1719            .proxy_write(ct)
1720            .await
1721            .map_err(Oauth2Error::ServerError)?;
1722        idms_prox_write
1723            .handle_oauth2_start_device_flow(client_auth_info, client_id, scope, eventid)
1724            .and_then(|res| {
1725                idms_prox_write.commit().map_err(Oauth2Error::ServerError)?;
1726                Ok(res)
1727            })
1728    }
1729}