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 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 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 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 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 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}