bios_iam/basic/serv/
iam_app_serv.rs

1use std::collections::HashSet;
2
3use async_trait::async_trait;
4use bios_basic::rbum::dto::rbum_rel_dto::RbumRelBoneResp;
5use bios_basic::rbum::serv::rbum_crud_serv::RbumCrudOperation;
6use bios_basic::rbum::serv::rbum_rel_serv::RbumRelServ;
7use bios_basic::rbum::serv::rbum_set_serv::RbumSetItemServ;
8use tardis::basic::dto::TardisContext;
9use tardis::basic::field::TrimString;
10use tardis::basic::result::TardisResult;
11use tardis::db::sea_orm::sea_query::{Expr, SelectStatement};
12use tardis::db::sea_orm::*;
13use tardis::futures_util::future::join_all;
14use tardis::{TardisFuns, TardisFunsInst};
15
16use bios_basic::rbum::dto::rbum_filer_dto::{RbumBasicFilterReq, RbumItemRelFilterReq, RbumSetItemFilterReq};
17use bios_basic::rbum::dto::rbum_item_dto::{RbumItemKernelAddReq, RbumItemKernelModifyReq};
18use bios_basic::rbum::helper::rbum_scope_helper;
19use bios_basic::rbum::rbum_enumeration::RbumRelFromKind;
20use bios_basic::rbum::serv::rbum_item_serv::RbumItemCrudOperation;
21
22use crate::basic::domain::iam_app;
23use crate::basic::dto::iam_app_dto::{IamAppAddReq, IamAppAggAddReq, IamAppAggModifyReq, IamAppDetailResp, IamAppModifyReq, IamAppSummaryResp};
24use crate::basic::dto::iam_filer_dto::IamAppFilterReq;
25use crate::basic::dto::iam_set_dto::IamSetItemAddReq;
26use crate::basic::serv::iam_cert_serv::IamCertServ;
27use crate::basic::serv::iam_key_cache_serv::IamIdentCacheServ;
28use crate::basic::serv::iam_rel_serv::IamRelServ;
29use crate::basic::serv::iam_role_serv::IamRoleServ;
30use crate::basic::serv::iam_set_serv::IamSetServ;
31use crate::iam_config::{IamBasicConfigApi, IamBasicInfoManager, IamConfig};
32use crate::iam_constants::{self, RBUM_SCOPE_LEVEL_PRIVATE};
33use crate::iam_constants::{RBUM_ITEM_ID_APP_LEN, RBUM_SCOPE_LEVEL_APP};
34use crate::iam_enumeration::{IamRelKind, IamSetKind};
35
36use super::clients::iam_kv_client::IamKvClient;
37pub struct IamAppServ;
38
39#[async_trait]
40impl RbumItemCrudOperation<iam_app::ActiveModel, IamAppAddReq, IamAppModifyReq, IamAppSummaryResp, IamAppDetailResp, IamAppFilterReq> for IamAppServ {
41    fn get_ext_table_name() -> &'static str {
42        iam_app::Entity.table_name()
43    }
44
45    fn get_rbum_kind_id() -> Option<String> {
46        Some(IamBasicInfoManager::get_config(|conf| conf.kind_app_id.clone()))
47    }
48
49    fn get_rbum_domain_id() -> Option<String> {
50        Some(IamBasicInfoManager::get_config(|conf| conf.domain_iam_id.clone()))
51    }
52
53    async fn package_item_add(add_req: &IamAppAddReq, _: &TardisFunsInst, _: &TardisContext) -> TardisResult<RbumItemKernelAddReq> {
54        Ok(RbumItemKernelAddReq {
55            id: add_req.id.clone(),
56            name: add_req.name.clone(),
57            disabled: add_req.disabled,
58            scope_level: add_req.scope_level.clone(),
59            ..Default::default()
60        })
61    }
62
63    async fn package_ext_add(id: &str, add_req: &IamAppAddReq, _: &TardisFunsInst, _: &TardisContext) -> TardisResult<iam_app::ActiveModel> {
64        Ok(iam_app::ActiveModel {
65            id: Set(id.to_string()),
66            icon: Set(add_req.icon.as_ref().unwrap_or(&"".to_string()).to_string()),
67            sort: Set(add_req.sort.unwrap_or(0)),
68            contact_phone: Set(add_req.contact_phone.as_ref().unwrap_or(&"".to_string()).to_string()),
69            ..Default::default()
70        })
71    }
72
73    async fn package_item_modify(_: &str, modify_req: &IamAppModifyReq, _: &TardisFunsInst, _: &TardisContext) -> TardisResult<Option<RbumItemKernelModifyReq>> {
74        if modify_req.name.is_none() && modify_req.scope_level.is_none() && modify_req.disabled.is_none() {
75            return Ok(None);
76        }
77        Ok(Some(RbumItemKernelModifyReq {
78            code: None,
79            name: modify_req.name.clone(),
80            scope_level: modify_req.scope_level.clone(),
81            disabled: modify_req.disabled,
82        }))
83    }
84
85    async fn package_ext_modify(id: &str, modify_req: &IamAppModifyReq, _: &TardisFunsInst, _: &TardisContext) -> TardisResult<Option<iam_app::ActiveModel>> {
86        if modify_req.icon.is_none() && modify_req.sort.is_none() && modify_req.contact_phone.is_none() {
87            return Ok(None);
88        }
89        let mut iam_app = iam_app::ActiveModel {
90            id: Set(id.to_string()),
91            ..Default::default()
92        };
93        if let Some(icon) = &modify_req.icon {
94            iam_app.icon = Set(icon.to_string());
95        }
96        if let Some(sort) = modify_req.sort {
97            iam_app.sort = Set(sort);
98        }
99        if let Some(contact_phone) = &modify_req.contact_phone {
100            iam_app.contact_phone = Set(contact_phone.to_string());
101        }
102        Ok(Some(iam_app))
103    }
104
105    async fn after_add_item(id: &str, _: &mut IamAppAddReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
106        #[cfg(feature = "spi_kv")]
107        Self::add_or_modify_app_kv(id, funs, ctx).await?;
108        Ok(())
109    }
110    async fn after_modify_item(id: &str, modify_req: &mut IamAppModifyReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
111        if modify_req.disabled.unwrap_or(false) {
112            IamIdentCacheServ::delete_tokens_and_contexts_by_tenant_or_app(id, true, funs, ctx).await?;
113        }
114        #[cfg(feature = "spi_kv")]
115        Self::add_or_modify_app_kv(id, funs, ctx).await?;
116        Ok(())
117    }
118
119    async fn before_delete_item(_: &str, funs: &TardisFunsInst, _: &TardisContext) -> TardisResult<Option<IamAppDetailResp>> {
120        Err(funs.err().conflict(&Self::get_obj_name(), "delete", "app can only be disabled but not deleted", "409-iam-app-can-not-delete"))
121    }
122
123    async fn package_ext_query(query: &mut SelectStatement, _: bool, filter: &IamAppFilterReq, _: &TardisFunsInst, _: &TardisContext) -> TardisResult<()> {
124        query.column((iam_app::Entity, iam_app::Column::ContactPhone));
125        query.column((iam_app::Entity, iam_app::Column::Icon));
126        query.column((iam_app::Entity, iam_app::Column::Sort));
127        if let Some(contact_phone) = &filter.contact_phone {
128            query.and_where(Expr::col(iam_app::Column::ContactPhone).eq(contact_phone.as_str()));
129        }
130        Ok(())
131    }
132}
133
134impl IamAppServ {
135    pub fn get_new_id() -> String {
136        TardisFuns::field.nanoid_len(RBUM_ITEM_ID_APP_LEN as usize)
137    }
138
139    pub fn is_app_level_by_ctx(ctx: &TardisContext) -> bool {
140        rbum_scope_helper::get_scope_level_by_context(ctx).unwrap_or(RBUM_SCOPE_LEVEL_PRIVATE) == RBUM_SCOPE_LEVEL_APP
141    }
142
143    pub fn get_id_by_ctx(ctx: &TardisContext, funs: &TardisFunsInst) -> TardisResult<String> {
144        if let Some(id) = rbum_scope_helper::get_path_item(RBUM_SCOPE_LEVEL_APP.to_int(), &ctx.own_paths) {
145            Ok(id)
146        } else {
147            Err(funs.err().unauthorized(
148                &Self::get_obj_name(),
149                "get_id",
150                &format!("app id not found in tardis content {}", ctx.own_paths),
151                "401-iam-app-context-not-exist",
152            ))
153        }
154    }
155
156    pub async fn add_app_agg(add_req: &IamAppAggAddReq, funs: &TardisFunsInst, tenant_ctx: &TardisContext) -> TardisResult<String> {
157        let app_id = Self::get_new_id();
158        let app_ctx = TardisContext {
159            own_paths: format!("{}/{}", tenant_ctx.own_paths, app_id),
160            ak: "".to_string(),
161            roles: vec![],
162            groups: vec![],
163            owner: tenant_ctx.owner.to_string(),
164            ..Default::default()
165        };
166        Self::add_item(
167            &mut IamAppAddReq {
168                id: Some(TrimString(app_id.clone())),
169                name: add_req.app_name.clone(),
170                icon: add_req.app_icon.clone(),
171                sort: add_req.app_sort,
172                contact_phone: add_req.app_contact_phone.clone(),
173                disabled: add_req.disabled,
174                scope_level: Some(iam_constants::RBUM_SCOPE_LEVEL_TENANT),
175            },
176            funs,
177            &app_ctx,
178        )
179        .await?;
180        IamRoleServ::add_app_copy_role_agg(&app_id, funs, &app_ctx).await?;
181        let app_admin_role_id = IamRoleServ::get_embed_sub_role_id(&funs.iam_basic_role_app_admin_id(), funs, &app_ctx).await?;
182        // TODO 是否需要在这里初始化应用级别的set?
183        IamSetServ::init_set(IamSetKind::Org, RBUM_SCOPE_LEVEL_APP, funs, &app_ctx).await?;
184        IamSetServ::init_set(IamSetKind::Apps, RBUM_SCOPE_LEVEL_APP, funs, &app_ctx).await?;
185        if let Some(admin_ids) = &add_req.admin_ids {
186            for admin_id in admin_ids {
187                IamAppServ::add_rel_account(&app_id, admin_id, false, funs, &app_ctx).await?;
188                IamRoleServ::add_rel_account(&app_admin_role_id, admin_id, None, funs, &app_ctx).await?;
189            }
190        }
191        //refresh ctx
192        let ctx = IamCertServ::use_sys_or_tenant_ctx_unsafe(tenant_ctx.clone())?;
193        IamCertServ::package_tardis_account_context_and_resp(&tenant_ctx.owner, &ctx.own_paths, "".to_string(), None, funs, &ctx).await?;
194
195        let apps_set_id = IamSetServ::get_default_set_id_by_ctx(&IamSetKind::Apps, funs, &ctx).await?;
196        IamSetServ::add_set_item(
197            &IamSetItemAddReq {
198                set_id: apps_set_id.clone(),
199                set_cate_id: add_req.set_cate_id.clone().unwrap_or_default(),
200                sort: add_req.app_sort.unwrap_or(0),
201                rel_rbum_item_id: app_id.to_string(),
202            },
203            funs,
204            &ctx,
205        )
206        .await?;
207        app_ctx.execute_task().await?;
208
209        Ok(app_id)
210    }
211
212    pub async fn modify_app_agg(id: &str, modify_req: &IamAppAggModifyReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
213        let app_admin_role_id = IamRoleServ::get_embed_sub_role_id(&funs.iam_basic_role_app_admin_id(), funs, ctx).await?;
214        let original_app_admin_account_ids = IamRoleServ::find_id_rel_accounts(&app_admin_role_id, None, None, funs, ctx).await?;
215        let original_app_admin_account_ids = HashSet::from_iter(original_app_admin_account_ids.iter().cloned());
216        Self::modify_item(
217            id,
218            &mut IamAppModifyReq {
219                name: modify_req.name.clone(),
220                scope_level: modify_req.scope_level.clone(),
221                disabled: modify_req.disabled,
222                icon: modify_req.icon.clone(),
223                sort: modify_req.sort,
224                contact_phone: modify_req.contact_phone.clone(),
225            },
226            funs,
227            ctx,
228        )
229        .await?;
230        if let Some(admin_ids) = &modify_req.admin_ids {
231            if !original_app_admin_account_ids.is_empty() {
232                // add new admins
233                for admin_id in admin_ids {
234                    if !original_app_admin_account_ids.contains(admin_id) {
235                        IamAppServ::add_rel_account(id, admin_id, true, funs, ctx).await?;
236                        IamRoleServ::add_rel_account(&app_admin_role_id, admin_id, None, funs, ctx).await?;
237                    }
238                }
239                // delete old admins
240                for account_id in original_app_admin_account_ids.difference(&admin_ids.iter().cloned().collect::<HashSet<String>>()) {
241                    IamRoleServ::delete_rel_account(&app_admin_role_id, account_id, None, funs, ctx).await?;
242                }
243            }
244        }
245        if let Some(set_cate_id) = &modify_req.set_cate_id {
246            let tenant_ctx = IamCertServ::use_sys_or_tenant_ctx_unsafe(ctx.clone())?;
247            let apps_set_id = IamSetServ::get_default_set_id_by_ctx(&IamSetKind::Apps, funs, &tenant_ctx).await?;
248            let set_items = IamSetServ::find_set_items(Some(apps_set_id.clone()), None, Some(id.to_owned()), None, true, Some(true), funs, &tenant_ctx).await?;
249            for set_item in set_items {
250                IamSetServ::delete_set_item(&set_item.id, funs, &tenant_ctx).await?;
251            }
252            IamSetServ::add_set_item(
253                &IamSetItemAddReq {
254                    set_id: apps_set_id.clone(),
255                    set_cate_id: set_cate_id.to_string(),
256                    sort: modify_req.sort.unwrap_or(0),
257                    rel_rbum_item_id: id.to_string(),
258                },
259                funs,
260                &tenant_ctx,
261            )
262            .await?;
263        }
264        if let Some(disabled) = &modify_req.disabled {
265            if *disabled {
266                join_all(
267                    RbumSetItemServ::find_id_rbums(
268                        &RbumSetItemFilterReq {
269                            rel_rbum_item_ids: Some(vec![id.to_string()]),
270                            ..Default::default()
271                        },
272                        None,
273                        None,
274                        funs,
275                        ctx,
276                    )
277                    .await?
278                    .into_iter()
279                    .map(|set_item_id| async move { RbumSetItemServ::delete_rbum(&set_item_id, funs, ctx).await }),
280                )
281                .await;
282            }
283        }
284        Ok(())
285    }
286
287    pub async fn find_rel_account(app_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<Vec<RbumRelBoneResp>> {
288        IamRelServ::find_to_simple_rels(&IamRelKind::IamAccountApp, app_id, None, None, funs, ctx).await
289    }
290
291    pub async fn add_rel_account(app_id: &str, account_id: &str, ignore_exist_error: bool, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
292        IamRelServ::add_simple_rel(&IamRelKind::IamAccountApp, account_id, app_id, None, None, ignore_exist_error, false, funs, ctx).await
293    }
294
295    pub async fn delete_rel_account(app_id: &str, account_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
296        IamRelServ::delete_simple_rel(&IamRelKind::IamAccountApp, account_id, app_id, funs, ctx).await?;
297        // TODO delete app rel account and role
298        let rel_account_roles =
299            RbumRelServ::find_from_simple_rels(&IamRelKind::IamAccountRole.to_string(), &RbumRelFromKind::Item, true, account_id, None, None, funs, ctx).await?;
300        if rel_account_roles.is_empty() {
301            return Ok(());
302        }
303        for rel in rel_account_roles {
304            IamRoleServ::delete_rel_account(&rel.rel_id, account_id, Some(RBUM_SCOPE_LEVEL_APP), funs, ctx).await?;
305        }
306        Ok(())
307    }
308
309    pub async fn count_rel_accounts(app_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<u64> {
310        IamRelServ::count_to_rels(&IamRelKind::IamAccountApp, app_id, funs, ctx).await
311    }
312
313    pub fn with_app_rel_filter(ctx: &TardisContext, funs: &TardisFunsInst) -> TardisResult<Option<RbumItemRelFilterReq>> {
314        Ok(Some(RbumItemRelFilterReq {
315            rel_by_from: true,
316            tag: Some(IamRelKind::IamAccountApp.to_string()),
317            from_rbum_kind: Some(RbumRelFromKind::Item),
318            rel_item_id: Some(Self::get_id_by_ctx(ctx, funs)?),
319            own_paths: Some(ctx.own_paths.clone()),
320            ..Default::default()
321        }))
322    }
323
324    pub async fn find_name_by_ids(filter: IamAppFilterReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<Vec<String>> {
325        IamAppServ::find_items(&filter, None, None, funs, ctx).await.map(|r| r.into_iter().map(|r| format!("{},{},{}", r.id, r.name, r.icon)).collect())
326    }
327
328    async fn add_or_modify_app_kv(app_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
329        let app = Self::get_item(
330            app_id,
331            &IamAppFilterReq {
332                basic: RbumBasicFilterReq {
333                    ignore_scope: true,
334                    own_paths: Some("".to_owned()),
335                    with_sub_own_paths: true,
336                    ..Default::default()
337                },
338                ..Default::default()
339            },
340            funs,
341            ctx,
342        )
343        .await?;
344        IamKvClient::add_or_modify_key_name(&funs.conf::<IamConfig>().spi.kv_app_prefix.clone(), app_id, &app.name, None, funs, ctx).await?;
345        Ok(())
346    }
347}