bios_iam/basic/serv/
iam_key_cache_serv.rs

1use std::default::Default;
2use std::str::FromStr;
3
4use bios_basic::helper::request_helper::{add_ip, get_real_ip_from_ctx};
5use bios_basic::process::task_processor::TaskProcessor;
6use bios_basic::rbum::rbum_config::RbumConfigApi;
7use bios_basic::rbum::serv::rbum_crud_serv::RbumCrudOperation;
8use itertools::Itertools;
9use serde::{Deserialize, Serialize};
10use tardis::basic::dto::TardisContext;
11use tardis::basic::result::TardisResult;
12use tardis::chrono::Utc;
13use tardis::{log, TardisFuns, TardisFunsInst};
14
15use bios_basic::rbum::dto::rbum_filer_dto::RbumBasicFilterReq;
16use bios_basic::rbum::serv::rbum_item_serv::{RbumItemCrudOperation, RbumItemServ};
17
18use crate::basic::dto::iam_account_dto::IamAccountInfoResp;
19use crate::basic::dto::iam_cert_dto::IamContextFetchReq;
20use crate::basic::dto::iam_filer_dto::{IamAccountFilterReq, IamAppFilterReq};
21use crate::basic::serv::clients::iam_log_client::{IamLogClient, LogParamTag};
22use crate::basic::serv::iam_account_serv::IamAccountServ;
23use crate::basic::serv::iam_app_serv::IamAppServ;
24use crate::basic::serv::iam_cert_serv::IamCertServ;
25use crate::basic::serv::iam_rel_serv::IamRelServ;
26use crate::iam_config::IamConfig;
27use crate::iam_constants::{self, IAM_AVATAR};
28use crate::iam_enumeration::{IamCertTokenKind, IamRelKind};
29pub struct IamIdentCacheServ;
30
31impl IamIdentCacheServ {
32    pub async fn add_token(
33        token: &str,
34        token_kind: &IamCertTokenKind,
35        rel_iam_item_id: &str,
36        renewal_expire_sec: Option<i64>,
37        expire_sec: i64,
38        coexist_num: i16,
39        funs: &TardisFunsInst,
40    ) -> TardisResult<()> {
41        let token_value = if let Some(renewal_expire_sec) = renewal_expire_sec {
42            format!("{token_kind},{rel_iam_item_id},{}", renewal_expire_sec)
43        } else {
44            format!("{token_kind},{rel_iam_item_id}")
45        };
46        log::trace!("add token: token={}", token);
47        if expire_sec > 0 {
48            funs.cache()
49                .set_ex(
50                    format!("{}{}", funs.conf::<IamConfig>().cache_key_token_info_, token).as_str(),
51                    token_value.as_str(),
52                    expire_sec as u64,
53                )
54                .await?;
55        } else {
56            funs.cache().set(format!("{}{}", funs.conf::<IamConfig>().cache_key_token_info_, token).as_str(), token_value.as_str()).await?;
57        }
58        funs.cache()
59            .hset(
60                format!("{}{}", funs.conf::<IamConfig>().cache_key_account_rel_, rel_iam_item_id).as_str(),
61                token,
62                &format!("{},{}", token_kind, Utc::now().timestamp_nanos_opt().expect("maybe in 23rd centery")),
63            )
64            .await?;
65        // Remove old tokens
66        if coexist_num != 0 {
67            let old_tokens = funs.cache().hgetall(format!("{}{}", funs.conf::<IamConfig>().cache_key_account_rel_, rel_iam_item_id).as_str()).await?;
68            let old_tokens = old_tokens
69                .into_iter()
70                .map(|(k, v)| {
71                    (
72                        k,
73                        v.split(',').next().unwrap_or("").to_string(),
74                        i64::from_str(v.split(',').nth(1).unwrap_or("")).unwrap_or(0),
75                    )
76                })
77                .filter(|(_, kind, _)| kind == token_kind.to_string().as_str())
78                .sorted_by(|(_, _, t1), (_, _, t2)| t2.cmp(t1))
79                .skip(coexist_num as usize)
80                .map(|(token, _, _)| token)
81                .collect::<Vec<String>>();
82            for old_token in old_tokens {
83                Self::delete_token_by_token(&old_token, None, funs).await?;
84            }
85        }
86        Ok(())
87    }
88
89    pub async fn delete_token_by_token(token: &str, ip: Option<String>, funs: &TardisFunsInst) -> TardisResult<()> {
90        log::trace!("delete token: token={}", token);
91        if let Some(token_info) = funs.cache().get(format!("{}{}", funs.conf::<IamConfig>().cache_key_token_info_, token).as_str()).await? {
92            let iam_item_id = token_info.split(',').nth(1).unwrap_or("");
93            funs.cache().del(format!("{}{}", funs.conf::<IamConfig>().cache_key_token_info_, token).as_str()).await?;
94            Self::delete_double_auth(iam_item_id, funs).await?;
95            funs.cache().hdel(format!("{}{}", funs.conf::<IamConfig>().cache_key_account_rel_, iam_item_id).as_str(), token).await?;
96
97            let mut mock_ctx = TardisContext::default();
98            if let Ok(account_context) = Self::get_account_context(iam_item_id, "", funs).await {
99                mock_ctx = account_context;
100            } else {
101                mock_ctx.owner = iam_item_id.to_string();
102                let own_paths = RbumItemServ::get_rbum(
103                    iam_item_id,
104                    &RbumBasicFilterReq {
105                        ignore_scope: true,
106                        with_sub_own_paths: true,
107                        own_paths: Some("".to_string()),
108                        ..Default::default()
109                    },
110                    funs,
111                    &mock_ctx,
112                )
113                .await?
114                .own_paths;
115                mock_ctx.own_paths = own_paths;
116            }
117            add_ip(ip, &mock_ctx).await?;
118
119            let _ = IamLogClient::add_ctx_task(
120                LogParamTag::IamAccount,
121                Some(iam_item_id.to_string()),
122                "下线账号".to_string(),
123                Some("OfflineAccount".to_string()),
124                &mock_ctx,
125            )
126            .await;
127            let _ = IamLogClient::add_ctx_task(
128                LogParamTag::SecurityVisit,
129                Some(iam_item_id.to_string()),
130                "退出".to_string(),
131                Some("Quit".to_string()),
132                &mock_ctx,
133            )
134            .await;
135
136            mock_ctx.execute_task().await?;
137        }
138        Ok(())
139    }
140
141    pub async fn delete_tokens_and_contexts_by_tenant_or_app(tenant_or_app_id: &str, is_app: bool, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
142        let tenant_or_app_id = tenant_or_app_id.to_string();
143        let own_paths = if is_app {
144            IamAppServ::peek_item(
145                &tenant_or_app_id,
146                &IamAppFilterReq {
147                    basic: RbumBasicFilterReq {
148                        with_sub_own_paths: true,
149                        ..Default::default()
150                    },
151                    ..Default::default()
152                },
153                funs,
154                ctx,
155            )
156            .await?
157            .own_paths
158        } else {
159            tenant_or_app_id.clone()
160        };
161        let ctx_clone = ctx.clone();
162        TaskProcessor::execute_task_with_ctx(
163            &funs.conf::<IamConfig>().cache_key_async_task_status,
164            move |_task_id| async move {
165                let funs = iam_constants::get_tardis_inst();
166                let filter = IamAccountFilterReq {
167                    basic: RbumBasicFilterReq {
168                        own_paths: Some(own_paths),
169                        with_sub_own_paths: true,
170                        ..Default::default()
171                    },
172                    ..Default::default()
173                };
174                let mut count = IamAccountServ::count_items(&filter, &funs, &ctx_clone).await.unwrap_or_default() as isize;
175                let mut page_number = 1;
176                while count > 0 {
177                    let mut ids = Vec::new();
178                    if let Ok(page) = IamAccountServ::paginate_id_items(&filter, page_number, 100, None, None, &funs, &ctx_clone).await {
179                        ids = page.records;
180                    }
181                    for id in ids {
182                        let account_context = Self::get_account_context(&id, "", &funs).await;
183                        if let Ok(account_context) = account_context {
184                            if account_context.own_paths == ctx_clone.own_paths {
185                                Self::delete_tokens_and_contexts_by_account_id(&id, get_real_ip_from_ctx(&ctx_clone).await?, &funs).await?;
186                            }
187                        }
188                    }
189                    page_number += 1;
190                    count -= 100;
191                }
192                if is_app {
193                    let mut count = IamRelServ::count_to_rels(&IamRelKind::IamAccountApp, &tenant_or_app_id, &funs, &ctx_clone).await.unwrap_or_default() as isize;
194                    let mut page_number = 1;
195                    while count > 0 {
196                        let mut ids = Vec::new();
197                        if let Ok(page) = IamRelServ::paginate_to_id_rels(&IamRelKind::IamAccountApp, &tenant_or_app_id, page_number, 100, None, None, &funs, &ctx_clone).await {
198                            ids = page.records;
199                        }
200                        for id in ids {
201                            let account_context = Self::get_account_context(&id, "", &funs).await;
202                            if let Ok(account_context) = account_context {
203                                if account_context.own_paths == ctx_clone.own_paths {
204                                    Self::delete_tokens_and_contexts_by_account_id(&id, get_real_ip_from_ctx(&ctx_clone).await?, &funs).await?;
205                                }
206                            }
207                        }
208                        page_number += 1;
209                        count -= 100;
210                    }
211                }
212                Ok(())
213            },
214            &funs.cache(),
215            IAM_AVATAR.to_owned(),
216            Some(vec![format!("account/{}", ctx.owner)]),
217            ctx,
218        )
219        .await?;
220        Ok(())
221    }
222
223    pub async fn delete_tokens_and_contexts_by_account_id(account_id: &str, ip: Option<String>, funs: &TardisFunsInst) -> TardisResult<()> {
224        log::trace!("delete tokens and contexts: account_id={}", account_id);
225        let tokens = funs.cache().hgetall(format!("{}{}", funs.conf::<IamConfig>().cache_key_account_rel_, account_id).as_str()).await?;
226        for (token, _) in tokens.iter() {
227            funs.cache().del(format!("{}{}", funs.conf::<IamConfig>().cache_key_token_info_, token).as_str()).await?;
228        }
229        funs.cache().del(format!("{}{}", funs.conf::<IamConfig>().cache_key_account_rel_, account_id).as_str()).await?;
230        funs.cache().del(format!("{}{}", funs.conf::<IamConfig>().cache_key_account_info_, account_id).as_str()).await?;
231
232        let mock_ctx = TardisContext { ..Default::default() };
233        add_ip(ip, &mock_ctx).await?;
234        let _ = IamLogClient::add_ctx_task(
235            LogParamTag::IamAccount,
236            Some(account_id.to_string()),
237            "下线账号".to_string(),
238            Some("OfflineAccount".to_string()),
239            &mock_ctx,
240        )
241        .await;
242
243        mock_ctx.execute_task().await?;
244
245        Ok(())
246    }
247
248    pub async fn exist_token_by_account_id(account_id: &str, funs: &TardisFunsInst) -> TardisResult<bool> {
249        log::trace!("exist tokens: account_id={}", account_id);
250        let tokens = funs.cache().hgetall(format!("{}{}", funs.conf::<IamConfig>().cache_key_account_rel_, account_id).as_str()).await?;
251        for (token, _) in tokens.iter() {
252            if funs.cache().exists(format!("{}{}", funs.conf::<IamConfig>().cache_key_token_info_, token).as_str()).await? {
253                return Ok(true);
254            }
255        }
256        Ok(false)
257    }
258
259    pub async fn refresh_account_info_by_account_id(account_id: &str, funs: &TardisFunsInst) -> TardisResult<()> {
260        log::trace!("refresh account info: account_id={}", account_id);
261        let tenant_info = funs.cache().hget(format!("{}{}", funs.conf::<IamConfig>().cache_key_account_info_, account_id).as_str(), "").await?;
262        if tenant_info.is_none() {
263            return Ok(());
264        }
265
266        let tenant_id = TardisFuns::json.str_to_obj::<TardisContext>(&tenant_info.unwrap())?.own_paths;
267        let mock_ctx = TardisContext {
268            own_paths: tenant_id.clone(),
269            ..Default::default()
270        };
271        let _ = IamCertServ::package_tardis_account_context_and_resp(account_id, &tenant_id, "".to_string(), None, funs, &mock_ctx).await;
272        Ok(())
273    }
274
275    pub async fn delete_lock_by_account_id(account_id: &str, funs: &TardisFunsInst) -> TardisResult<()> {
276        log::trace!("delete lock: account_id={}", account_id);
277        funs.cache().del(&format!("{}{}", funs.rbum_conf_cache_key_cert_locked_(), &account_id)).await?;
278        Ok(())
279    }
280
281    pub async fn add_contexts(account_info: &IamAccountInfoResp, tenant_id: &str, funs: &TardisFunsInst) -> TardisResult<()> {
282        log::trace!("add contexts: account_id={:?}", account_info);
283        funs.cache()
284            .hset(
285                format!("{}{}", funs.conf::<IamConfig>().cache_key_account_info_, account_info.account_id).as_str(),
286                "",
287                &TardisFuns::json.obj_to_string(&TardisContext {
288                    own_paths: tenant_id.to_string(),
289                    owner: account_info.account_id.to_string(),
290                    roles: account_info.roles.keys().map(|id| id.to_string()).collect(),
291                    groups: account_info.groups.keys().map(|id| id.to_string()).collect(),
292                    ..Default::default()
293                })?,
294            )
295            .await?;
296        for account_app_info in &account_info.apps {
297            funs.cache()
298                .hset(
299                    format!("{}{}", funs.conf::<IamConfig>().cache_key_account_info_, account_info.account_id).as_str(),
300                    &account_app_info.app_id,
301                    &TardisFuns::json.obj_to_string(&TardisContext {
302                        own_paths: format!("{}/{}", tenant_id, account_app_info.app_id).to_string(),
303                        owner: account_info.account_id.to_string(),
304                        roles: account_app_info.roles.keys().map(|id| id.to_string()).collect(),
305                        groups: account_app_info.groups.keys().map(|id| id.to_string()).collect(),
306                        ..Default::default()
307                    })?,
308                )
309                .await?;
310        }
311        Ok(())
312    }
313
314    pub async fn get_account_context(account_id: &str, field: &str, funs: &TardisFunsInst) -> TardisResult<TardisContext> {
315        let mut context = if let Some(context) = funs.cache().hget(format!("{}{}", funs.conf::<IamConfig>().cache_key_account_info_, account_id).as_str(), field).await? {
316            TardisFuns::json.str_to_obj::<TardisContext>(&context)?
317        } else {
318            return Err(funs.err().not_found("get_account_context", "get", "not found context", "404-iam-cache-context-not-exist"));
319        };
320        if !field.is_empty() {
321            if let Some(tenant_context) = funs.cache().hget(&format!("{}{}", funs.conf::<IamConfig>().cache_key_account_info_, account_id), "").await? {
322                let tenant_context = TardisFuns::json.str_to_obj::<TardisContext>(&tenant_context)?;
323                if !tenant_context.roles.is_empty() {
324                    context.roles.extend(tenant_context.roles);
325                }
326                if !tenant_context.groups.is_empty() {
327                    context.groups.extend(tenant_context.groups);
328                }
329            }
330        }
331        Ok(context)
332    }
333
334    pub async fn get_context(fetch_req: &IamContextFetchReq, funs: &TardisFunsInst) -> TardisResult<TardisContext> {
335        if let Some(token_info) = funs.cache().get(format!("{}{}", funs.conf::<IamConfig>().cache_key_token_info_, &fetch_req.token).as_str()).await? {
336            let account_id = token_info.split(',').nth(1).unwrap_or("");
337            if let Some(context) = funs
338                .cache()
339                .hget(
340                    format!("{}{}", funs.conf::<IamConfig>().cache_key_account_info_, account_id).as_str(),
341                    fetch_req.app_id.as_ref().unwrap_or(&"".to_string()),
342                )
343                .await?
344            {
345                return TardisFuns::json.str_to_obj(&context);
346            }
347        }
348        Err(funs.err().not_found("iam_cache_context", "get", "not found context", "404-iam-cache-context-not-exist"))
349    }
350
351    pub async fn add_aksk(ak: &str, sk: &str, tenant_id: &str, app_id: Option<String>, expire_sec: i64, funs: &TardisFunsInst) -> TardisResult<()> {
352        log::trace!("add aksk: ak={},sk={}", ak, sk);
353        if expire_sec > 0 {
354            funs.cache()
355                .set_ex(
356                    format!("{}{}", funs.conf::<IamConfig>().cache_key_aksk_info_, ak).as_str(),
357                    format!("{},{},{}", sk, tenant_id, app_id.unwrap_or_default()).as_str(),
358                    expire_sec as u64,
359                )
360                .await?;
361        } else {
362            funs.cache()
363                .set(
364                    format!("{}{}", funs.conf::<IamConfig>().cache_key_aksk_info_, ak).as_str(),
365                    format!("{},{},{}", sk, tenant_id, app_id.unwrap_or_default()).as_str(),
366                )
367                .await?;
368        }
369        Ok(())
370    }
371
372    pub async fn add_or_modify_gateway_rule_info(ak: &str, rule_name: &str, match_method: Option<&str>, value: &str, funs: &TardisFunsInst) -> TardisResult<()> {
373        log::trace!(
374            "add gateway_rule_info: ak={},rule_name={},match_method={},value={}",
375            ak,
376            rule_name,
377            match_method.unwrap_or("*"),
378            value
379        );
380        let match_path = &funs.conf::<IamConfig>().gateway_openapi_path;
381        funs.cache()
382            .set(
383                format!(
384                    "{}{}:{}:{}:{}",
385                    funs.conf::<IamConfig>().cache_key_gateway_rule_info_,
386                    rule_name,
387                    match_method.unwrap_or("*"),
388                    match_path,
389                    ak
390                )
391                .as_str(),
392                value,
393            )
394            .await?;
395        Ok(())
396    }
397
398    pub async fn get_gateway_cumulative_count(ak: &str, match_method: Option<&str>, funs: &TardisFunsInst) -> TardisResult<Option<String>> {
399        let match_path = &funs.conf::<IamConfig>().gateway_openapi_path;
400        let result = funs
401            .cache()
402            .get(&format!(
403                "{}{}:{}:{}:{}:cumulative-count",
404                funs.conf::<IamConfig>().cache_key_gateway_rule_info_,
405                iam_constants::OPENAPI_GATEWAY_PLUGIN_COUNT,
406                match_method.unwrap_or("*"),
407                match_path,
408                ak
409            ))
410            .await?;
411        Ok(result)
412    }
413
414    pub async fn get_gateway_rule_info(ak: &str, rule_name: &str, match_method: Option<&str>, funs: &TardisFunsInst) -> TardisResult<Option<String>> {
415        let match_path = &funs.conf::<IamConfig>().gateway_openapi_path;
416        let result = funs
417            .cache()
418            .get(
419                format!(
420                    "{}{}:{}:{}:{}",
421                    funs.conf::<IamConfig>().cache_key_gateway_rule_info_,
422                    rule_name,
423                    match_method.unwrap_or("*"),
424                    match_path,
425                    ak
426                )
427                .as_str(),
428            )
429            .await?;
430        Ok(result)
431    }
432
433    pub async fn delete_aksk(ak: &str, funs: &TardisFunsInst) -> TardisResult<()> {
434        log::trace!("delete aksk: ak={}", ak);
435
436        funs.cache().del(format!("{}{}", funs.conf::<IamConfig>().cache_key_aksk_info_, ak).as_str()).await?;
437
438        Ok(())
439    }
440
441    pub async fn add_double_auth(account_id: &str, funs: &TardisFunsInst) -> TardisResult<()> {
442        log::trace!("add double auth: account_id={}", account_id);
443
444        funs.cache()
445            .set_ex(
446                format!("{}{}", funs.conf::<IamConfig>().cache_key_double_auth_info, account_id).as_str(),
447                "",
448                funs.conf::<IamConfig>().cache_key_double_auth_expire_sec as u64,
449            )
450            .await?;
451
452        Ok(())
453    }
454    pub async fn delete_double_auth(account_id: &str, funs: &TardisFunsInst) -> TardisResult<()> {
455        log::trace!("delete double auth: account_id={}", account_id);
456        if (funs.cache().get(format!("{}{}", funs.conf::<IamConfig>().cache_key_double_auth_info, account_id).as_str()).await?).is_some() {
457            funs.cache().del(format!("{}{}", funs.conf::<IamConfig>().cache_key_double_auth_info, account_id).as_str()).await?;
458        }
459        Ok(())
460    }
461}
462
463pub struct IamResCacheServ;
464
465impl IamResCacheServ {
466    pub async fn add_res(item_code: &str, action: &str, crypto_req: bool, crypto_resp: bool, double_auth: bool, need_login: bool, funs: &TardisFunsInst) -> TardisResult<()> {
467        let uri_mixed = Self::package_uri_mixed(item_code, action);
468        log::trace!("add res: uri_mixed={}", uri_mixed);
469        let add_res_dto = IamCacheResRelAddOrModifyDto {
470            need_crypto_req: crypto_req,
471            need_crypto_resp: crypto_resp,
472            need_double_auth: double_auth,
473            need_login,
474            ..Default::default()
475        };
476        funs.cache().hset(&funs.conf::<IamConfig>().cache_key_res_info, &uri_mixed, &TardisFuns::json.obj_to_string(&add_res_dto)?).await?;
477        Self::add_change_trigger(&uri_mixed, funs).await
478    }
479
480    pub async fn delete_res(item_code: &str, action: &str, funs: &TardisFunsInst) -> TardisResult<()> {
481        let uri_mixed = Self::package_uri_mixed(item_code, action);
482        log::trace!("delete res: uri_mixed={}", uri_mixed);
483        funs.cache().hdel(&funs.conf::<IamConfig>().cache_key_res_info, &uri_mixed).await?;
484        Self::add_change_trigger(&uri_mixed, funs).await
485    }
486
487    // add anonymous access permissions
488    pub async fn add_anonymous_res_rel(item_code: &str, action: &str, st: Option<i64>, et: Option<i64>, funs: &TardisFunsInst) -> TardisResult<()> {
489        let res_auth = IamCacheResAuth {
490            tenants: "#*#".to_string(),
491            st,
492            et,
493            ..Default::default()
494        };
495        let mut res_dto = IamCacheResRelAddOrModifyDto {
496            auth: Some(res_auth),
497            need_crypto_req: false,
498            need_crypto_resp: false,
499            need_double_auth: false,
500            need_login: false,
501        };
502        let uri_mixed = Self::package_uri_mixed(item_code, action);
503        let rels = funs.cache().hget(&funs.conf::<IamConfig>().cache_key_res_info, &uri_mixed).await?;
504        if let Some(rels) = rels {
505            let old_res_dto = TardisFuns::json.str_to_obj::<IamCacheResRelAddOrModifyDto>(&rels)?;
506            res_dto.need_crypto_req = old_res_dto.need_crypto_req;
507            res_dto.need_crypto_resp = old_res_dto.need_crypto_resp;
508            res_dto.need_double_auth = old_res_dto.need_double_auth;
509            res_dto.need_login = old_res_dto.need_login;
510        }
511        funs.cache().hset(&funs.conf::<IamConfig>().cache_key_res_info, &uri_mixed, &TardisFuns::json.obj_to_string(&res_dto)?).await?;
512        Self::add_change_trigger(&uri_mixed, funs).await
513    }
514
515    pub async fn add_or_modify_res_rel(item_code: &str, action: &str, add_or_modify_req: &IamCacheResRelAddOrModifyReq, funs: &TardisFunsInst) -> TardisResult<()> {
516        if add_or_modify_req.st.is_some() || add_or_modify_req.et.is_some() {
517            // TODO supports time range
518            return Err(funs.err().conflict("iam_cache_res", "add_or_modify", "st and et must be none", "409-iam-cache-res-date-not-none"));
519        }
520        let mut res_auth = IamCacheResAuth {
521            accounts: format!("#{}#", add_or_modify_req.accounts.join("#")),
522            roles: format!("#{}#", add_or_modify_req.roles.join("#")),
523            groups: format!("#{}#", add_or_modify_req.groups.join("#")),
524            apps: format!("#{}#", add_or_modify_req.apps.join("#")),
525            tenants: format!("#{}#", add_or_modify_req.tenants.join("#")),
526            aks: format!("#{}#", add_or_modify_req.aks.join("#")),
527            ..Default::default()
528        };
529        let mut res_dto = IamCacheResRelAddOrModifyDto {
530            auth: None,
531            need_crypto_req: false,
532            need_crypto_resp: false,
533            need_double_auth: false,
534            need_login: false,
535        };
536        let uri_mixed = Self::package_uri_mixed(item_code, action);
537        log::trace!("add or modify res rel: uri_mixed={}", uri_mixed);
538        let rels = funs.cache().hget(&funs.conf::<IamConfig>().cache_key_res_info, &uri_mixed).await?;
539        if let Some(rels) = rels {
540            let old_res_dto = TardisFuns::json.str_to_obj::<IamCacheResRelAddOrModifyDto>(&rels)?;
541            if let Some(old_auth) = old_res_dto.auth {
542                res_auth.accounts = format!("{}{}", res_auth.accounts, old_auth.accounts);
543                res_auth.roles = format!("{}{}", res_auth.roles, old_auth.roles);
544                res_auth.groups = format!("{}{}", res_auth.groups, old_auth.groups);
545                res_auth.apps = format!("{}{}", res_auth.apps, old_auth.apps);
546                res_auth.tenants = format!("{}{}", res_auth.tenants, old_auth.tenants);
547                res_auth.aks = format!("{}{}", res_auth.aks, old_auth.aks);
548            }
549
550            if let Some(need_crypto_req) = add_or_modify_req.need_crypto_req {
551                res_dto.need_crypto_req = need_crypto_req
552            } else {
553                res_dto.need_crypto_req = old_res_dto.need_crypto_req
554            }
555            if let Some(need_crypto_resp) = add_or_modify_req.need_crypto_resp {
556                res_dto.need_crypto_resp = need_crypto_resp
557            } else {
558                res_dto.need_crypto_resp = old_res_dto.need_crypto_resp
559            }
560            if let Some(need_double_auth) = add_or_modify_req.need_double_auth {
561                res_dto.need_double_auth = need_double_auth
562            } else {
563                res_dto.need_double_auth = old_res_dto.need_double_auth
564            }
565            if let Some(need_login) = add_or_modify_req.need_login {
566                res_dto.need_login = need_login
567            } else {
568                res_dto.need_login = old_res_dto.need_login
569            }
570        }
571        res_auth.accounts = res_auth.accounts.replace("##", "#");
572        res_auth.roles = res_auth.roles.replace("##", "#");
573        res_auth.groups = res_auth.groups.replace("##", "#");
574        res_auth.apps = res_auth.apps.replace("##", "#");
575        res_auth.tenants = res_auth.tenants.replace("##", "#");
576        res_auth.aks = res_auth.aks.replace("##", "#");
577
578        if (res_auth.accounts == "#" || res_auth.accounts == "##")
579            && (res_auth.roles == "#" || res_auth.roles == "##")
580            && (res_auth.groups == "#" || res_auth.groups == "##")
581            && (res_auth.apps == "#" || res_auth.apps == "##")
582            && (res_auth.tenants == "#" || res_auth.tenants == "##")
583            && (res_auth.aks == "#" || res_auth.aks == "##")
584        {
585            res_dto.auth = None;
586        } else {
587            res_dto.auth = Some(res_auth);
588        }
589
590        funs.cache().hset(&funs.conf::<IamConfig>().cache_key_res_info, &uri_mixed, &TardisFuns::json.obj_to_string(&res_dto)?).await?;
591        Self::add_change_trigger(&uri_mixed, funs).await
592    }
593
594    pub async fn delete_res_rel(item_code: &str, action: &str, delete_req: &IamCacheResRelDeleteReq, funs: &TardisFunsInst) -> TardisResult<()> {
595        let uri_mixed = Self::package_uri_mixed(item_code, action);
596        log::trace!("delete res rel: uri_mixed={}", uri_mixed);
597        let rels = funs.cache().hget(&funs.conf::<IamConfig>().cache_key_res_info, &uri_mixed).await?;
598        if let Some(rels) = rels {
599            let mut res_dto = TardisFuns::json.str_to_obj::<IamCacheResRelAddOrModifyDto>(&rels)?;
600            if let Some(res_auth) = res_dto.auth {
601                let mut auth = res_auth;
602                for account in &delete_req.accounts {
603                    while auth.accounts.contains(&format!("#{account}#")) {
604                        auth.accounts = auth.accounts.replacen(&format!("#{account}#"), "#", 1);
605                    }
606                }
607                for role in &delete_req.roles {
608                    while auth.roles.contains(&format!("#{role}#")) {
609                        auth.roles = auth.roles.replacen(&format!("#{role}#"), "#", 1);
610                    }
611                }
612                for group in &delete_req.groups {
613                    while auth.groups.contains(&format!("#{group}#")) {
614                        auth.groups = auth.groups.replacen(&format!("#{group}#"), "#", 1);
615                    }
616                }
617                for app in &delete_req.apps {
618                    while auth.apps.contains(&format!("#{app}#")) {
619                        auth.apps = auth.apps.replacen(&format!("#{app}#"), "#", 1);
620                    }
621                }
622                for tenant in &delete_req.tenants {
623                    while auth.tenants.contains(&format!("#{tenant}#")) {
624                        auth.tenants = auth.tenants.replacen(&format!("#{tenant}#"), "#", 1);
625                    }
626                }
627                for ak in &delete_req.aks {
628                    while auth.aks.contains(&format!("#{ak}#")) {
629                        auth.aks = auth.aks.replacen(&format!("#{ak}#"), "#", 1);
630                    }
631                }
632                if (auth.accounts == "#" || auth.accounts == "##")
633                    && (auth.roles == "#" || auth.roles == "##")
634                    && (auth.groups == "#" || auth.groups == "##")
635                    && (auth.apps == "#" || auth.apps == "##")
636                    && (auth.tenants == "#" || auth.tenants == "##")
637                {
638                    res_dto.auth = None;
639                } else {
640                    res_dto.auth = Some(auth);
641                }
642            }
643            funs.cache().hset(&funs.conf::<IamConfig>().cache_key_res_info, &uri_mixed, &TardisFuns::json.obj_to_string(&res_dto)?).await?;
644            return Self::add_change_trigger(&uri_mixed, funs).await;
645        }
646        Err(funs.err().not_found("iam_cache_res", "delete", "not found res rel", "404-iam-cache-res-rel-not-exist"))
647    }
648
649    async fn add_change_trigger(uri: &str, funs: &TardisFunsInst) -> TardisResult<()> {
650        funs.cache()
651            .set_ex(
652                &format!("{}{}", funs.conf::<IamConfig>().cache_key_res_changed_info_, uri),
653                "",
654                funs.conf::<IamConfig>().cache_key_res_changed_expire_sec as u64,
655            )
656            .await?;
657        Ok(())
658    }
659
660    pub fn package_uri_mixed(item_code: &str, action: &str) -> String {
661        let domain_idx = item_code.find('/').unwrap_or(item_code.len());
662        let domain = &item_code[0..domain_idx];
663        let path_and_query = if domain_idx != item_code.len() { &item_code[domain_idx + 1..] } else { "" };
664        format!(
665            "{}://{}/{}##{}",
666            iam_constants::RBUM_KIND_CODE_IAM_RES.to_lowercase(),
667            domain.to_lowercase(),
668            path_and_query,
669            action
670        )
671    }
672}
673
674#[derive(Serialize, Deserialize, Debug, Clone, Default)]
675#[serde(default)]
676struct IamCacheResRelAddOrModifyDto {
677    pub auth: Option<IamCacheResAuth>,
678    pub need_crypto_req: bool,
679    pub need_crypto_resp: bool,
680    pub need_double_auth: bool,
681    pub need_login: bool,
682}
683#[derive(Serialize, Deserialize, Debug, Clone, Default)]
684#[serde(default)]
685struct IamCacheResAuth {
686    pub accounts: String,
687    pub roles: String,
688    pub groups: String,
689    pub apps: String,
690    pub tenants: String,
691    pub aks: String,
692    pub st: Option<i64>,
693    pub et: Option<i64>,
694}
695
696pub struct IamCacheResRelAddOrModifyReq {
697    pub st: Option<i64>,
698    pub et: Option<i64>,
699    pub accounts: Vec<String>,
700    pub roles: Vec<String>,
701    pub groups: Vec<String>,
702    pub apps: Vec<String>,
703    pub tenants: Vec<String>,
704    pub aks: Vec<String>,
705    pub need_crypto_req: Option<bool>,
706    pub need_crypto_resp: Option<bool>,
707    pub need_double_auth: Option<bool>,
708    pub need_login: Option<bool>,
709}
710
711#[derive(Deserialize, Serialize, Clone, Debug)]
712pub struct IamCacheResRelDeleteReq {
713    pub accounts: Vec<String>,
714    pub roles: Vec<String>,
715    pub groups: Vec<String>,
716    pub apps: Vec<String>,
717    pub tenants: Vec<String>,
718    pub aks: Vec<String>,
719}