bios_iam/basic/serv/
iam_cert_ldap_serv.rs

1use self::ldap::LdapClient;
2use super::clients::iam_log_client::{IamLogClient, LogParamTag};
3use super::clients::iam_search_client::IamSearchClient;
4use super::iam_cert_phone_vcode_serv::IamCertPhoneVCodeServ;
5use super::{iam_account_serv::IamAccountServ, iam_cert_serv::IamCertServ, iam_tenant_serv::IamTenantServ};
6use crate::basic::dto::iam_account_dto::{IamAccountAddByLdapResp, IamAccountAggModifyReq, IamAccountExtSysAddReq, IamAccountExtSysBatchAddReq};
7use crate::basic::dto::iam_cert_dto::IamCertMailVCodeAddReq;
8use crate::basic::dto::iam_cert_dto::{IamCertPhoneVCodeAddReq, IamThirdIntegrationConfigDto, IamThirdIntegrationSyncStatusDto};
9use crate::basic::dto::iam_filer_dto::IamAccountFilterReq;
10use crate::basic::serv::iam_cert_mail_vcode_serv::IamCertMailVCodeServ;
11use crate::basic::serv::iam_cert_user_pwd_serv::IamCertUserPwdServ;
12use crate::console_passport::dto::iam_cp_cert_dto::IamCpUserPwdBindWithLdapReq;
13use crate::console_passport::serv::iam_cp_cert_user_pwd_serv::IamCpCertUserPwdServ;
14use crate::iam_enumeration::{IamAccountLogoutTypeKind, IamAccountStatusKind, IamCertExtKind, IamCertKernelKind, WayToAdd, WayToDelete};
15use crate::{
16    basic::dto::{
17        iam_account_dto::{IamAccountAggAddReq, IamAccountExtSysResp},
18        iam_cert_conf_dto::{IamCertConfLdapAddOrModifyReq, IamCertConfLdapResp},
19        iam_cert_dto::IamCertLdapAddOrModifyReq,
20        iam_filer_dto::IamTenantFilterReq,
21    },
22    iam_config::IamBasicConfigApi,
23    iam_constants,
24};
25use bios_basic::helper::request_helper::get_real_ip_from_ctx;
26use bios_basic::rbum::dto::rbum_cert_dto::RbumCertSummaryResp;
27use bios_basic::rbum::dto::rbum_filer_dto::RbumBasicFilterReq;
28use bios_basic::rbum::rbum_enumeration::RbumCertStatusKind::Enabled;
29use bios_basic::rbum::rbum_enumeration::{RbumCertConfStatusKind, RbumScopeLevelKind};
30use bios_basic::rbum::{
31    dto::{
32        rbum_cert_conf_dto::{RbumCertConfAddReq, RbumCertConfModifyReq},
33        rbum_cert_dto::{RbumCertAddReq, RbumCertModifyReq},
34        rbum_filer_dto::{RbumCertConfFilterReq, RbumCertFilterReq},
35    },
36    rbum_enumeration::{RbumCertRelKind, RbumCertStatusKind},
37    serv::{
38        rbum_cert_serv::{RbumCertConfServ, RbumCertServ},
39        rbum_crud_serv::RbumCrudOperation,
40        rbum_item_serv::RbumItemCrudOperation,
41    },
42};
43use ldap3::log::{error, warn};
44use serde::{Deserialize, Serialize};
45use std::collections::HashMap;
46
47use crate::iam_config::IamConfig;
48use tardis::regex::Regex;
49use tardis::web::poem_openapi;
50use tardis::{
51    basic::{dto::TardisContext, field::TrimString, result::TardisResult},
52    TardisFuns, TardisFunsInst,
53};
54
55pub struct IamCertLdapServ;
56
57impl IamCertLdapServ {
58    //ldap only can be one recode in each tenant
59    pub async fn add_cert_conf(add_req: &IamCertConfLdapAddOrModifyReq, rel_iam_item_id: Option<String>, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<String> {
60        Self::validate_cert_conf(add_req, funs).await?;
61        let result = RbumCertConfServ::add_rbum(
62            &mut RbumCertConfAddReq {
63                kind: TrimString(IamCertExtKind::Ldap.to_string()),
64                supplier: add_req.supplier.clone(),
65                name: TrimString(add_req.name.clone()),
66                note: None,
67                ak_note: None,
68                ak_rule: None,
69                sk_note: None,
70                sk_rule: None,
71                ext: Some(Self::iam_cert_ldap_server_auth_info_to_json(add_req)?),
72                sk_need: Some(false),
73                sk_dynamic: Some(false),
74                sk_encrypted: Some(false),
75                repeatable: None,
76                is_basic: Some(false),
77                rest_by_kinds: None,
78                expire_sec: None,
79                sk_lock_cycle_sec: None,
80                sk_lock_err_times: None,
81                sk_lock_duration_sec: None,
82                coexist_num: Some(1),
83                conn_uri: Some(add_req.conn_uri.clone()),
84                status: RbumCertConfStatusKind::Enabled,
85                rel_rbum_domain_id: funs.iam_basic_domain_iam_id(),
86                rel_rbum_item_id: rel_iam_item_id.clone(),
87            },
88            funs,
89            ctx,
90        )
91        .await;
92
93        if result.is_ok() {
94            let _ = IamLogClient::add_ctx_task(
95                LogParamTag::IamAccount,
96                Some(ctx.owner.clone()),
97                "绑定账号".to_string(),
98                Some("BindAccount".to_string()),
99                ctx,
100            )
101            .await;
102        }
103
104        result
105    }
106
107    pub async fn modify_cert_conf(id: &str, modify_req: &IamCertConfLdapAddOrModifyReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
108        Self::validate_cert_conf(modify_req, funs).await?;
109        let result = RbumCertConfServ::modify_rbum(
110            id,
111            &mut RbumCertConfModifyReq {
112                name: None,
113                note: None,
114                ak_note: None,
115                ak_rule: None,
116                sk_note: None,
117                sk_rule: None,
118                ext: Some(Self::iam_cert_ldap_server_auth_info_to_json(modify_req)?),
119                sk_need: None,
120                sk_encrypted: None,
121                repeatable: None,
122                is_basic: None,
123                rest_by_kinds: None,
124                expire_sec: None,
125                sk_lock_cycle_sec: None,
126                sk_lock_err_times: None,
127                sk_lock_duration_sec: None,
128                coexist_num: None,
129                conn_uri: Some(modify_req.conn_uri.clone()),
130                status: None,
131            },
132            funs,
133            ctx,
134        )
135        .await;
136        if result.is_ok() {
137            let _ = IamLogClient::add_ctx_task(
138                LogParamTag::IamAccount,
139                Some(ctx.owner.clone()),
140                "绑定5A账号".to_string(),
141                Some("Bind5aAccount".to_string()),
142                ctx,
143            )
144            .await;
145        }
146        result
147    }
148
149    //验证cert conf配置是否正确
150    pub async fn validate_cert_conf(add_req: &IamCertConfLdapAddOrModifyReq, funs: &TardisFunsInst) -> TardisResult<()> {
151        let ldap_auth_info = IamCertLdapServerAuthInfo::from((*add_req).clone());
152        let mut ldap_client = LdapClient::new(
153            &add_req.conn_uri,
154            ldap_auth_info.port,
155            ldap_auth_info.is_tls,
156            ldap_auth_info.timeout,
157            &ldap_auth_info.base_dn,
158        )
159        .await
160        .map_err(|e| {
161            funs.err().bad_request(
162                "IamCertLdap",
163                "add",
164                &format!("add cert conf err: ldap conf parameter error,and err:{e}"),
165                "400-iam--ldap-cert-add-parameter-incorrect",
166            )
167        })?;
168        if ldap_client.bind_by_dn(&ldap_auth_info.principal, &ldap_auth_info.credentials).await?.is_none() {
169            ldap_client.unbind().await?;
170            return Err(funs.err().unauthorized("ldap_cert_conf", "add", "validation error", "401-rbum-cert-valid-error"));
171        }
172        ldap_client.with_limit(1)?;
173        let result = ldap_client
174            .page_search(
175                5,
176                &ldap_auth_info.account_field_map.search_base_filter.unwrap_or("objectClass=person".to_string()),
177                &vec![
178                    "dn",
179                    &ldap_auth_info.account_field_map.field_user_name,
180                    &ldap_auth_info.account_field_map.field_display_name,
181                ],
182            )
183            .await?;
184        if let Some(result) = result.first() {
185            if result.get_simple_attr(&ldap_auth_info.account_field_map.field_user_name).is_none() {
186                return Err(funs.err().bad_request(
187                    "ldap_conf",
188                    "validate",
189                    &format!("ldap not have user_name field:{}", ldap_auth_info.account_field_map.field_user_name),
190                    "404-iam-ldap-user_name-valid-error",
191                ));
192            };
193            if result.get_simple_attr(&ldap_auth_info.account_field_map.field_display_name).is_none() {
194                return Err(funs.err().bad_request(
195                    "ldap_conf",
196                    "validate",
197                    &format!("ldap not have display_name field:{}", ldap_auth_info.account_field_map.field_display_name),
198                    "404-iam-ldap-display_name-valid-error",
199                ));
200            }
201        }
202        ldap_client.unbind().await?;
203        Ok(())
204    }
205
206    pub async fn get_cert_conf_by_ctx(funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<Option<IamCertConfLdapResp>> {
207        if let Some(resp) = RbumCertConfServ::find_one_rbum(
208            &RbumCertConfFilterReq {
209                basic: RbumBasicFilterReq {
210                    own_paths: Some(ctx.own_paths.clone()),
211                    ..Default::default()
212                },
213                kind: Some(TrimString("Ldap".to_string())),
214                status: Some(RbumCertConfStatusKind::Enabled),
215                rel_rbum_domain_id: Some(funs.iam_basic_domain_iam_id()),
216                rel_rbum_item_id: Some(ctx.own_paths.clone()),
217                ..Default::default()
218            },
219            funs,
220            ctx,
221        )
222        .await?
223        {
224            let result = TardisFuns::json.str_to_obj::<IamCertLdapServerAuthInfo>(&resp.ext).map(|info| IamCertConfLdapResp {
225                id: resp.id,
226                supplier: resp.supplier,
227                conn_uri: resp.conn_uri,
228                is_tls: info.is_tls,
229                timeout: info.timeout,
230                principal: info.principal,
231                credentials: info.credentials,
232                base_dn: info.base_dn,
233                port: info.port,
234                account_unique_id: info.account_unique_id,
235                account_field_map: info.account_field_map,
236                // org_unique_id: info.org_unique_id,
237                // org_field_map: info.org_field_map,
238            })?;
239            Ok(Some(result))
240        } else {
241            Ok(None)
242        }
243    }
244
245    pub async fn get_cert_conf(id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<IamCertConfLdapResp> {
246        RbumCertConfServ::get_rbum(id, &RbumCertConfFilterReq::default(), funs, ctx).await.map(|resp| {
247            TardisFuns::json.str_to_obj::<IamCertLdapServerAuthInfo>(&resp.ext).map(|info| IamCertConfLdapResp {
248                id: resp.id,
249                supplier: resp.supplier,
250                conn_uri: resp.conn_uri,
251                is_tls: info.is_tls,
252                timeout: info.timeout,
253                principal: info.principal,
254                credentials: info.credentials,
255                base_dn: info.base_dn,
256                port: info.port,
257                account_unique_id: info.account_unique_id,
258                account_field_map: info.account_field_map,
259                // org_unique_id: info.org_unique_id,
260                // org_field_map: info.org_field_map,
261            })
262        })?
263    }
264
265    pub async fn add_or_modify_cert(
266        add_or_modify_req: &IamCertLdapAddOrModifyReq,
267        account_id: &str,
268        rel_rbum_cert_conf_id: &str,
269        funs: &TardisFunsInst,
270        ctx: &TardisContext,
271    ) -> TardisResult<()> {
272        let cert_id = RbumCertServ::find_id_rbums(
273            &RbumCertFilterReq {
274                rel_rbum_cert_conf_ids: Some(vec![rel_rbum_cert_conf_id.to_string()]),
275                rel_rbum_id: Some(account_id.to_string()),
276                ..Default::default()
277            },
278            None,
279            None,
280            funs,
281            ctx,
282        )
283        .await?;
284        if let Some(cert_id) = cert_id.first() {
285            RbumCertServ::modify_rbum(
286                cert_id,
287                &mut RbumCertModifyReq {
288                    ak: Some(add_or_modify_req.ldap_id.clone()),
289                    sk: None,
290                    sk_invisible: None,
291                    ignore_check_sk: false,
292                    ext: None,
293                    start_time: None,
294                    end_time: None,
295                    conn_uri: None,
296                    status: None,
297                },
298                funs,
299                ctx,
300            )
301            .await?;
302        } else {
303            RbumCertServ::add_rbum(
304                &mut RbumCertAddReq {
305                    ak: add_or_modify_req.ldap_id.clone(),
306                    sk: None,
307                    sk_invisible: None,
308                    kind: None,
309                    supplier: None,
310                    vcode: None,
311                    ext: None,
312                    start_time: None,
313                    end_time: None,
314                    conn_uri: None,
315                    status: RbumCertStatusKind::Enabled,
316                    rel_rbum_cert_conf_id: Some(rel_rbum_cert_conf_id.to_string()),
317                    rel_rbum_kind: RbumCertRelKind::Item,
318                    rel_rbum_id: account_id.to_string(),
319                    is_outside: false,
320                    ignore_check_sk: false,
321                },
322                funs,
323                ctx,
324            )
325            .await?;
326        };
327        Ok(())
328    }
329
330    ///获取dn对应的account_id
331    pub async fn get_cert_rel_account_by_dn(dn: &str, rel_rbum_cert_conf_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<Option<String>> {
332        let result = RbumCertServ::find_rbums(
333            &RbumCertFilterReq {
334                rel_rbum_cert_conf_ids: Some(vec![rel_rbum_cert_conf_id.to_string()]),
335                ak: Some(dn.to_string()),
336                ..Default::default()
337            },
338            None,
339            None,
340            funs,
341            ctx,
342        )
343        .await?
344        .first()
345        .map(|r| r.rel_rbum_id.to_string());
346        Ok(result)
347    }
348
349    pub async fn batch_get_or_add_account_without_verify(
350        add_req: IamAccountExtSysBatchAddReq,
351        tenant_id: Option<String>,
352        funs: &TardisFunsInst,
353        ctx: &TardisContext,
354    ) -> TardisResult<IamAccountAddByLdapResp> {
355        if add_req.account_id.is_empty() {
356            Ok(IamAccountAddByLdapResp {
357                result: vec![],
358                fail: HashMap::new(),
359            })
360        } else {
361            let mut result: Vec<String> = Vec::new();
362            let mut fail: HashMap<String, String> = HashMap::new();
363            for account_id in add_req.account_id {
364                let verify = Self::get_or_add_account_without_verify(
365                    IamAccountExtSysAddReq {
366                        account_id: account_id.clone(),
367                        code: add_req.code.clone(),
368                    },
369                    tenant_id.clone(),
370                    funs,
371                    ctx,
372                )
373                .await;
374                if verify.is_ok() {
375                    result.push(verify.unwrap_or_default().0);
376                } else {
377                    let err_msg = if let Err(tardis_error) = verify { tardis_error.message } else { "".to_string() };
378                    warn!("get_or_add_account_without_verify resp is err:{}", err_msg);
379                    fail.insert(account_id, err_msg);
380                }
381            }
382            Ok(IamAccountAddByLdapResp { result, fail })
383        }
384    }
385
386    ///根据add_req的account_id(dn)获取或者添加账号
387    /// 始终返回(account_id,dn)
388    pub async fn get_or_add_account_without_verify(
389        add_req: IamAccountExtSysAddReq,
390        tenant_id: Option<String>,
391        funs: &TardisFunsInst,
392        ctx: &TardisContext,
393    ) -> TardisResult<(String, String)> {
394        let dn = &add_req.account_id;
395        let cert_conf_id = IamCertServ::get_cert_conf_id_by_kind_supplier(&IamCertExtKind::Ldap.to_string(), &add_req.code.clone(), tenant_id, funs).await?;
396        let cert_conf = Self::get_cert_conf(&cert_conf_id, funs, ctx).await?;
397        if let Some(account_id) = Self::get_cert_rel_account_by_dn(dn, &cert_conf_id, funs, ctx).await? {
398            return Ok((account_id, dn.to_string()));
399        }
400        let mut ldap_client = LdapClient::new(&cert_conf.conn_uri, cert_conf.port, cert_conf.is_tls, cert_conf.timeout, &cert_conf.base_dn).await?;
401        if ldap_client.bind_by_dn(&cert_conf.principal, &cert_conf.credentials).await?.is_none() {
402            ldap_client.unbind().await?;
403            return Err(funs.err().unauthorized("rbum_cert", "search_accounts", "ldap admin validation error", "401-rbum-cert-valid-error"));
404        };
405        let account = ldap_client
406            .get_by_dn(
407                dn,
408                &vec!["dn", "cn", &cert_conf.account_field_map.field_user_name, &cert_conf.account_field_map.field_display_name],
409            )
410            .await?;
411        ldap_client.unbind().await?;
412        if let Some(account) = account {
413            let mock_ctx = TardisContext {
414                own_paths: ctx.own_paths.clone(),
415                owner: TardisFuns::field.nanoid(),
416                ..ctx.clone()
417            };
418            let account_id = Self::do_add_account(
419                &account.dn,
420                &account.get_simple_attr(&cert_conf.account_field_map.field_display_name).unwrap_or_default(),
421                &account.get_simple_attr(&cert_conf.account_field_map.field_user_name).unwrap_or_default(),
422                &format!("{}0Pw$", TardisFuns::field.nanoid_len(6)),
423                &cert_conf_id,
424                &account.get_simple_attr(&cert_conf.account_field_map.field_labor_type).unwrap_or_default(),
425                RbumCertStatusKind::Enabled,
426                funs,
427                &mock_ctx,
428            )
429            .await?;
430            mock_ctx.execute_task().await?;
431            Ok((account_id, dn.to_string()))
432        } else {
433            return Err(funs.err().not_found(
434                "rbum_cert",
435                "get_or_add_account_without_verify",
436                &format!("not found ldap cert(openid): {}", &dn),
437                "401-rbum-cert-valid-error",
438            ));
439        }
440    }
441
442    pub async fn get_account_with_verify(user_name: &str, password: &str, tenant_id: Option<String>, code: &str, funs: &TardisFunsInst) -> TardisResult<Option<(String, String)>> {
443        let mock_ctx = Self::generate_default_mock_ctx(code, tenant_id.clone(), funs).await;
444        let (mut ldap_client, _, cert_conf_id) = Self::get_ldap_client(Some(mock_ctx.own_paths.clone()), code, funs, &mock_ctx).await?;
445        let dn = if let Some(dn) = ldap_client.bind(user_name, password).await? {
446            dn
447        } else {
448            ldap_client.unbind().await?;
449            return Err(funs.err().unauthorized("rbum_cert", "get_or_add_account", "validation error", "401-rbum-usrpwd-cert-valid-error"));
450        };
451        ldap_client.unbind().await?;
452        if let Some(account_id) = Self::get_cert_rel_account_by_dn(&dn, &cert_conf_id, funs, &mock_ctx).await? {
453            Ok(Some((account_id, dn)))
454        } else {
455            Ok(None)
456        }
457    }
458
459    pub async fn search_accounts(
460        user_or_display_name: &str,
461        tenant_id: Option<String>,
462        code: &str,
463        funs: &TardisFunsInst,
464        ctx: &TardisContext,
465    ) -> TardisResult<Vec<IamAccountExtSysResp>> {
466        let (mut ldap_client, cert_conf, _) = Self::get_ldap_client(tenant_id, code, funs, ctx).await?;
467        if ldap_client.bind_by_dn(&cert_conf.principal, &cert_conf.credentials).await?.is_none() {
468            ldap_client.unbind().await?;
469            return Err(funs.err().unauthorized("rbum_cert", "search_accounts", "ldap admin validation error", "401-rbum-cert-valid-error"));
470        };
471        let accounts = ldap_client
472            .search(
473                &cert_conf.package_filter_by_fuzzy_search_account(user_or_display_name),
474                &cert_conf.package_account_return_attr_with(vec!["dn", "cn"]),
475            )
476            .await?
477            .into_iter()
478            .map(|r| IamAccountExtSysResp::form_ldap_search_resp(r, &cert_conf))
479            .collect();
480        ldap_client.unbind().await?;
481        Ok(accounts)
482    }
483
484    pub async fn check_user_pwd_is_bind(ak: &str, supplier: &str, tenant_id: Option<String>, funs: &TardisFunsInst) -> TardisResult<bool> {
485        if tenant_id.is_some() && IamTenantServ::is_disabled(&tenant_id.clone().unwrap_or_default(), funs).await? {
486            return Err(funs.err().conflict(
487                "user_pwd",
488                "check_bind",
489                &format!("tenant {} is disabled", tenant_id.unwrap_or_default()),
490                "409-iam-tenant-is-disabled",
491            ));
492        }
493        let tenant_ldap_cert_conf_id_result = IamCertServ::get_cert_conf_id_by_kind_supplier(&IamCertExtKind::Ldap.to_string(), supplier, tenant_id.clone(), funs).await;
494        let global_ldap_cert_conf_id_result = IamCertServ::get_cert_conf_id_by_kind_supplier(&IamCertExtKind::Ldap.to_string(), supplier, None, funs).await;
495        if tenant_ldap_cert_conf_id_result.is_err() && global_ldap_cert_conf_id_result.is_err() {
496            return Ok(false);
497        }
498        let tenant_userpwd_cert_conf_id = IamCertServ::get_cert_conf_id_by_kind(&IamCertKernelKind::UserPwd.to_string(), tenant_id.clone(), funs).await?;
499        let tenant_exist = RbumCertServ::check_exist(ak, &tenant_userpwd_cert_conf_id, &tenant_id.clone().unwrap_or_default(), funs).await?;
500        //if tenant have cert_conf,then use tenant level
501        let (ldap_cert_conf_id, userpwd_cert_conf_id, userpwd_cert_exist) = if tenant_ldap_cert_conf_id_result.is_ok() {
502            (tenant_ldap_cert_conf_id_result?, tenant_userpwd_cert_conf_id, tenant_exist)
503        } else {
504            let userpwd_cert_conf_id = IamCertServ::get_cert_conf_id_by_kind(&IamCertKernelKind::UserPwd.to_string(), None, funs).await?;
505            let global_userpwd_exist = RbumCertServ::check_exist(ak, &userpwd_cert_conf_id, "", funs).await?;
506            let exist = if tenant_id.is_some() && !global_userpwd_exist {
507                if tenant_exist {
508                    return Err(funs.err().conflict("user_pwd", "check_bind", "user is private", "409-user-is-private"));
509                } else {
510                    false
511                }
512            } else {
513                true
514            };
515            (global_ldap_cert_conf_id_result?, userpwd_cert_conf_id, exist)
516        };
517
518        if userpwd_cert_exist {
519            let mock_ctx = Self::generate_default_mock_ctx(supplier, tenant_id.clone(), funs).await;
520            if let Some(account_id) = IamCpCertUserPwdServ::get_cert_rel_account_by_user_name(ak, &userpwd_cert_conf_id, funs, &mock_ctx).await? {
521                let cert_id = Self::get_ldap_cert_account_by_account(&account_id, &ldap_cert_conf_id, funs, &mock_ctx).await?.first().map(|r| r.id.to_string());
522                if cert_id.is_some() {
523                    Ok(true)
524                } else {
525                    Ok(false)
526                }
527            } else {
528                // Unreachable code
529                error!("function:check_bind,code should not be executed");
530                Ok(false)
531            }
532        } else {
533            Err(funs.err().not_found("user_pwd", "check_bind", "not found cert record", "404-rbum-*-obj-not-exist"))
534        }
535    }
536
537    pub async fn validate_by_ldap(sk: &str, supplier: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<bool> {
538        let (mut ldap_client, _cert_conf, cert_conf_id) = Self::get_ldap_client(Some(ctx.own_paths.clone()), supplier, funs, ctx).await?;
539        let certs = Self::get_ldap_cert_account_by_account(&ctx.owner, &cert_conf_id, funs, ctx).await?;
540        if let Some(cert) = certs.first() {
541            let result = ldap_client.bind_by_dn(&cert.ak, sk).await?;
542            if result.is_some() {
543                ldap_client.unbind().await?;
544                Ok(true)
545            } else {
546                ldap_client.unbind().await?;
547                Err(funs.err().unauthorized("ldap cert", "valid", "validation error", "401-rbum-cert-valid-error"))
548            }
549        } else {
550            Err(funs.err().not_found("ldap", "validate_sk", "not found cert record", "404-rbum-*-obj-not-exist"))
551        }
552    }
553
554    pub async fn bind_or_create_user_pwd_by_ldap(login_req: &IamCpUserPwdBindWithLdapReq, funs: &TardisFunsInst) -> TardisResult<(String, String)> {
555        let tenant_id = login_req.tenant_id.clone();
556        // mock_ctx decide whether the login mode is global or tenant level
557        let mut mock_ctx = Self::generate_default_mock_ctx(login_req.ldap_login.code.as_ref(), tenant_id.clone(), funs).await;
558
559        let (mut ldap_client, cert_conf, cert_conf_id) =
560            Self::get_ldap_client(Some(mock_ctx.own_paths.clone()), login_req.ldap_login.code.to_string().as_str(), funs, &mock_ctx).await?;
561        let dn = if let Some(dn) = ldap_client.bind(login_req.ldap_login.name.to_string().as_str(), login_req.ldap_login.password.as_str()).await? {
562            dn
563        } else {
564            ldap_client.unbind().await?;
565            return Err(funs.err().unauthorized("rbum_cert", "get_or_add_account", "validation error", "401-rbum-cert-valid-error"));
566        };
567
568        let account = ldap_client.get_by_dn(&dn, &cert_conf.package_account_return_attr_with(vec!["dn", "cn"])).await?;
569        ldap_client.unbind().await?;
570        if let Some(account) = account {
571            mock_ctx.owner = TardisFuns::field.nanoid();
572            let account_id = if let Some(ak) = login_req.bind_user_pwd.ak.clone() {
573                // bind user_pwd with ldap cert
574                Self::bind_user_pwd_by_ldap(
575                    &account.get_simple_attr(&cert_conf.account_unique_id).unwrap_or_default(),
576                    ak.as_ref(),
577                    login_req.bind_user_pwd.sk.as_ref(),
578                    &cert_conf_id,
579                    tenant_id.clone(),
580                    &login_req.ldap_login.code,
581                    funs,
582                    &mock_ctx,
583                )
584                .await?
585            } else {
586                // create user_pwd and bind user_pwd with ldap cert
587                if tenant_id.is_some() && !IamTenantServ::get_item(&tenant_id.unwrap_or_default(), &IamTenantFilterReq::default(), funs, &mock_ctx).await?.account_self_reg {
588                    return Err(funs.err().not_found(
589                        "rbum_cert",
590                        "create_user_pwd_by_ldap",
591                        &format!("not found ldap cert(openid): {} and self-registration disabled", &dn),
592                        "401-rbum-cert-valid-error",
593                    ));
594                }
595
596                Self::do_add_account(
597                    &dn,
598                    &account.get_simple_attr(&cert_conf.account_field_map.field_display_name).unwrap_or_default(),
599                    &account.get_simple_attr(&cert_conf.account_field_map.field_user_name).unwrap_or_default(),
600                    login_req.bind_user_pwd.sk.as_ref(),
601                    &cert_conf_id,
602                    &account.get_simple_attr(&cert_conf.account_field_map.field_labor_type).unwrap_or_default(),
603                    RbumCertStatusKind::Enabled,
604                    funs,
605                    &mock_ctx,
606                )
607                .await?
608            };
609            mock_ctx.execute_task().await?;
610            Ok((account_id, dn))
611        } else {
612            return Err(funs.err().not_found(
613                "rbum_cert",
614                "bind_or_create_user_pwd_by_ldap",
615                &format!("not found ldap cert(openid): {}", &dn),
616                "401-rbum-cert-valid-error",
617            ));
618        }
619    }
620
621    pub async fn bind_user_pwd_by_ldap(
622        ldap_id: &str,
623        user_name: &str,
624        password: &str,
625        cert_conf_id: &str,
626        tenant_id: Option<String>,
627        code: &str,
628        funs: &TardisFunsInst,
629        ctx: &TardisContext,
630    ) -> TardisResult<String> {
631        //验证用户名密码登录
632        let (_, _, rbum_item_id) = if let Some(tenant_id) = tenant_id.clone() {
633            let global_check = IamCertServ::validate_by_ak_and_sk(
634                user_name,
635                password,
636                None,
637                Some(&RbumCertRelKind::Item),
638                false,
639                Some("".to_string()),
640                Some(vec![&IamCertKernelKind::UserPwd.to_string()]),
641                get_real_ip_from_ctx(ctx).await?,
642                funs,
643            )
644            .await;
645            if global_check.is_err() {
646                let tenant_check = IamCertServ::validate_by_ak_and_sk(
647                    user_name,
648                    password,
649                    None,
650                    Some(&RbumCertRelKind::Item),
651                    false,
652                    Some(tenant_id.to_string()),
653                    Some(vec![&IamCertKernelKind::UserPwd.to_string()]),
654                    get_real_ip_from_ctx(ctx).await?,
655                    funs,
656                )
657                .await;
658                if tenant_check.is_ok() && ctx.own_paths.is_empty() {
659                    return Err(funs.err().conflict("rbum_cert", "bind_user_pwd_by_ldap", "user is private", "409-user-is-private"));
660                } else if tenant_check.is_err() {
661                    return Err(funs.err().unauthorized("rbum_cert", "valid", "validation error", "401-rbum-cert-valid-error"));
662                } else {
663                    tenant_check?
664                }
665            } else {
666                global_check?
667            }
668        } else {
669            IamCertServ::validate_by_ak_and_sk(
670                user_name,
671                password,
672                None,
673                Some(&RbumCertRelKind::Item),
674                false,
675                Some("".to_string()),
676                Some(vec![&IamCertKernelKind::UserPwd.to_string()]),
677                get_real_ip_from_ctx(ctx).await?,
678                funs,
679            )
680            .await?
681        };
682        if Self::check_user_pwd_is_bind(user_name, code, tenant_id.clone(), funs).await? {
683            return Err(funs.err().not_found("rbum_cert", "bind_user_pwd_by_ldap", "user is bound by ldap", "409-iam-user-is-bound"));
684        }
685        //添加这个用户的ldap登录cert
686        Self::add_or_modify_cert(
687            &IamCertLdapAddOrModifyReq {
688                ldap_id: TrimString(ldap_id.to_string()),
689                status: RbumCertStatusKind::Enabled,
690            },
691            &rbum_item_id,
692            cert_conf_id,
693            funs,
694            ctx,
695        )
696        .await?;
697        Ok(rbum_item_id)
698    }
699
700    //同步ldap人员到iam
701    pub async fn iam_sync_ldap_user_to_iam(sync_config: IamThirdIntegrationConfigDto, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<String> {
702        let _ = funs
703            .cache()
704            .set(
705                &funs.conf::<IamConfig>().cache_key_sync_ldap_status,
706                &TardisFuns::json.obj_to_string(&IamThirdIntegrationSyncStatusDto { total: 0, success: 0, failed: 0 })?,
707            )
708            .await;
709        let mut msg = "".to_string();
710        let (mut ldap_client, cert_conf, cert_conf_id) = Self::get_ldap_client(Some(ctx.own_paths.clone()), "", funs, ctx).await?;
711        if ldap_client.bind_by_dn(&cert_conf.principal, &cert_conf.credentials).await?.is_none() {
712            ldap_client.unbind().await?;
713            return Err(funs.err().unauthorized("ldap_cert_conf", "add", "validation error", "401-rbum-cert-valid-error"));
714        }
715        let mut ldap_id_to_account_map = HashMap::new();
716        let ldap_account: Vec<IamAccountExtSysResp> = ldap_client
717            .page_search(
718                100,
719                &cert_conf.account_field_map.search_base_filter.clone().unwrap_or("objectClass=person".to_string()),
720                &cert_conf.package_account_return_attr_with(vec!["dn"]),
721            )
722            .await?
723            .into_iter()
724            .map(|r| IamAccountExtSysResp::form_ldap_search_resp(r, &cert_conf))
725            .collect();
726        ldap_account.iter().for_each(|r| {
727            ldap_id_to_account_map.insert(r.account_id.clone(), r);
728        });
729
730        let (mut total, mut success, mut failed) = (ldap_account.len(), 0, 0);
731
732        let _ = funs
733            .cache()
734            .set(
735                &funs.conf::<IamConfig>().cache_key_sync_ldap_status,
736                &TardisFuns::json.obj_to_string(&IamThirdIntegrationSyncStatusDto { total, success, failed })?,
737            )
738            .await;
739        let _ = ldap_client.unbind().await;
740
741        let certs = IamCertServ::find_certs(
742            &RbumCertFilterReq {
743                basic: RbumBasicFilterReq {
744                    own_paths: Some(ctx.own_paths.clone()),
745                    with_sub_own_paths: true,
746                    ..Default::default()
747                },
748                status: Some(Enabled),
749                rel_rbum_cert_conf_ids: Some(vec![cert_conf_id.clone()]),
750                ..Default::default()
751            },
752            None,
753            None,
754            funs,
755            ctx,
756        )
757        .await?;
758        for cert in certs {
759            let mut funs = iam_constants::get_tardis_inst();
760            let local_ldap_id = cert.ak;
761            if let Some(iam_account_ext_sys_resp) = ldap_id_to_account_map.get(&local_ldap_id) {
762                //并集 两边都有相同的账号
763                let local_account_result = IamAccountServ::find_one_item(
764                    &IamAccountFilterReq {
765                        basic: RbumBasicFilterReq {
766                            ids: Some(vec![cert.rel_rbum_id.clone()]),
767                            ..Default::default()
768                        },
769                        ..Default::default()
770                    },
771                    &funs,
772                    ctx,
773                )
774                .await;
775                if local_account_result.is_err() || local_account_result.clone()?.is_none() {
776                    let err_msg = format!("get user info failed, id:{} ", cert.rel_rbum_id);
777                    msg = format!("{msg}{err_msg}\n");
778                    ldap_id_to_account_map.remove(&local_ldap_id);
779                    continue;
780                }
781                let local_account = local_account_result.unwrap_or_default().unwrap();
782                // 在事务外单独更新用工性质字段
783                if !iam_account_ext_sys_resp.labor_type.is_empty() && iam_account_ext_sys_resp.labor_type != local_account.labor_type {
784                    let account_modify_req = IamAccountAggModifyReq {
785                        labor_type: Some(iam_account_ext_sys_resp.labor_type.clone()),
786                        ..Default::default()
787                    };
788                    let modify_result = IamAccountServ::modify_account_agg(&cert.rel_rbum_id, &account_modify_req, &funs, ctx).await;
789                    if modify_result.is_err() {
790                        let err_msg = format!("modify account labor_type id:{} failed:{}", cert.rel_rbum_id, modify_result.err().unwrap());
791                        tardis::log::error!("{}", err_msg);
792                        msg = format!("{msg}{err_msg}\n");
793                        ldap_id_to_account_map.remove(&local_ldap_id);
794                        continue;
795                    }
796                    IamSearchClient::add_or_modify_account_search(
797                        IamAccountServ::get_account_detail_aggs(
798                            &cert.rel_rbum_id,
799                            &IamAccountFilterReq {
800                                basic: RbumBasicFilterReq {
801                                    ignore_scope: true,
802                                    own_paths: Some("".to_string()),
803                                    with_sub_own_paths: true,
804                                    ..Default::default()
805                                },
806                                ..Default::default()
807                            },
808                            true,
809                            true,
810                            &funs,
811                            ctx,
812                        )
813                        .await?,
814                        Box::new(true),
815                        "",
816                        &funs,
817                        ctx,
818                    )
819                    .await?;
820                }
821                funs.begin().await?;
822                //判断是否需要更新status等
823                //如果需要更新其他信息,比如用户名也写在这里面
824                if local_account.disabled || local_account.status == IamAccountStatusKind::Logout {
825                    let mut account_modify_req = IamAccountAggModifyReq::default();
826                    if local_account.disabled || local_account.status == IamAccountStatusKind::Logout {
827                        account_modify_req.status = Some(IamAccountStatusKind::Active);
828                        account_modify_req.logout_type = Some(IamAccountLogoutTypeKind::NotLogout);
829                        account_modify_req.disabled = Some(false);
830                    }
831                    let modify_result = IamAccountServ::modify_account_agg(&cert.rel_rbum_id, &account_modify_req, &funs, ctx).await;
832                    if modify_result.is_err() {
833                        let err_msg = format!("modify account info id:{} failed:{}", cert.rel_rbum_id, modify_result.err().unwrap());
834                        tardis::log::error!("{}", err_msg);
835                        msg = format!("{msg}{err_msg}\n");
836                        funs.rollback().await?;
837                        ldap_id_to_account_map.remove(&local_ldap_id);
838                        continue;
839                    }
840                    IamSearchClient::add_or_modify_account_search(
841                        IamAccountServ::get_account_detail_aggs(
842                            &cert.rel_rbum_id,
843                            &IamAccountFilterReq {
844                                basic: RbumBasicFilterReq {
845                                    ignore_scope: true,
846                                    own_paths: Some("".to_string()),
847                                    with_sub_own_paths: true,
848                                    ..Default::default()
849                                },
850                                ..Default::default()
851                            },
852                            true,
853                            true,
854                            &funs,
855                            ctx,
856                        )
857                        .await?,
858                        Box::new(true),
859                        "",
860                        &funs,
861                        ctx,
862                    )
863                    .await?;
864                }
865
866                if !iam_account_ext_sys_resp.mobile.is_empty() {
867                    // 如果有手机号配置那么就更新手机号
868                    let phone_cert_conf_id = IamCertServ::get_cert_conf_id_by_kind(&IamCertKernelKind::PhoneVCode.to_string(), Some(ctx.own_paths.clone()), &funs).await?;
869                    if let Some(phone_cert) = RbumCertServ::find_one_rbum(
870                        &RbumCertFilterReq {
871                            basic: RbumBasicFilterReq {
872                                own_paths: Some(ctx.own_paths.clone()),
873                                with_sub_own_paths: true,
874                                ignore_scope: true,
875                                ..Default::default()
876                            },
877                            status: Some(RbumCertStatusKind::Enabled),
878                            rel_rbum_kind: Some(RbumCertRelKind::Item),
879                            rel_rbum_id: Some(cert.rel_rbum_id.clone()),
880                            rel_rbum_cert_conf_ids: Some(vec![phone_cert_conf_id.clone()]),
881                            ..Default::default()
882                        },
883                        &funs,
884                        ctx,
885                    )
886                    .await?
887                    {
888                        if phone_cert.ak != iam_account_ext_sys_resp.mobile {
889                            let modify_result: Result<(), tardis::basic::error::TardisError> = RbumCertServ::modify_rbum(
890                                &phone_cert.id,
891                                &mut RbumCertModifyReq {
892                                    ak: Some(TrimString(iam_account_ext_sys_resp.mobile.clone())),
893                                    sk: None,
894                                    ignore_check_sk: false,
895                                    ext: None,
896                                    start_time: None,
897                                    end_time: None,
898                                    conn_uri: None,
899                                    status: None,
900                                    sk_invisible: None,
901                                },
902                                &funs,
903                                ctx,
904                            )
905                            .await;
906                            if let Some(e) = modify_result.err() {
907                                let err_msg = format!("sync account:{} modify phone cert_id:{} failed:{}", cert.rel_rbum_id, phone_cert.id, e);
908                                tardis::log::error!("{}", err_msg);
909                                msg = format!("{msg}{err_msg}\n");
910                            }
911                        }
912                    } else {
913                        //添加手机号
914                        if let Err(e) = IamCertPhoneVCodeServ::add_cert_skip_vcode(
915                            &IamCertPhoneVCodeAddReq {
916                                phone: TrimString(iam_account_ext_sys_resp.mobile.clone()),
917                            },
918                            cert.rel_rbum_id.as_str(),
919                            phone_cert_conf_id.as_str(),
920                            &funs,
921                            ctx,
922                        )
923                        .await
924                        {
925                            let err_msg = format!("sync account:{} add phone phone:{} failed:{}", cert.rel_rbum_id, iam_account_ext_sys_resp.mobile.clone(), e);
926                            tardis::log::error!("{}", err_msg);
927                            msg = format!("{msg}{err_msg}\n");
928                            failed += 1;
929                            ldap_id_to_account_map.remove(&local_ldap_id);
930                            continue;
931                        }
932                    }
933                }
934                if !iam_account_ext_sys_resp.email.is_empty() {
935                    // 如果有配置邮箱那么就更新邮箱
936                    let email_cert_conf_id = IamCertServ::get_cert_conf_id_by_kind(&IamCertKernelKind::MailVCode.to_string(), Some(ctx.own_paths.clone()), &funs).await?;
937                    if let Some(email_cert) = RbumCertServ::find_one_rbum(
938                        &RbumCertFilterReq {
939                            basic: RbumBasicFilterReq {
940                                own_paths: Some(ctx.own_paths.clone()),
941                                with_sub_own_paths: true,
942                                ignore_scope: true,
943                                ..Default::default()
944                            },
945                            status: Some(RbumCertStatusKind::Enabled),
946                            rel_rbum_kind: Some(RbumCertRelKind::Item),
947                            rel_rbum_id: Some(cert.rel_rbum_id.clone()),
948                            rel_rbum_cert_conf_ids: Some(vec![email_cert_conf_id.clone()]),
949                            ..Default::default()
950                        },
951                        &funs,
952                        ctx,
953                    )
954                    .await?
955                    {
956                        // 更新邮箱
957                        if email_cert.ak != iam_account_ext_sys_resp.email {
958                            let modify_result = RbumCertServ::modify_rbum(
959                                &email_cert.id,
960                                &mut RbumCertModifyReq {
961                                    ak: Some(TrimString(iam_account_ext_sys_resp.email.clone())),
962                                    sk: None,
963                                    ignore_check_sk: false,
964                                    ext: None,
965                                    start_time: None,
966                                    end_time: None,
967                                    conn_uri: None,
968                                    status: None,
969                                    sk_invisible: None,
970                                },
971                                &funs,
972                                ctx,
973                            )
974                            .await;
975                            if let Some(e) = modify_result.err() {
976                                let err_msg = format!("sync account:{} modify email cert_id:{} failed:{}", cert.rel_rbum_id, email_cert.id, e);
977                                tardis::log::error!("{}", err_msg);
978                                msg = format!("{msg}{err_msg}\n");
979                            }
980                        }
981                    } else {
982                        //添加邮箱
983                        if let Err(e) = IamCertMailVCodeServ::add_cert_skip_activate(
984                            &IamCertMailVCodeAddReq {
985                                mail: TrimString(iam_account_ext_sys_resp.email.clone()).to_string(),
986                            },
987                            cert.rel_rbum_id.as_str(),
988                            &email_cert_conf_id,
989                            &funs,
990                            ctx,
991                        )
992                        .await
993                        {
994                            let err_msg = format!("sync account:{} add email email:{} failed:{}", cert.rel_rbum_id, iam_account_ext_sys_resp.email.clone(), e);
995                            tardis::log::error!("{}", err_msg);
996                            msg = format!("{msg}{err_msg}\n");
997                            failed += 1;
998                            ldap_id_to_account_map.remove(&local_ldap_id);
999                            continue;
1000                        }
1001                    };
1002                }
1003
1004                ldap_id_to_account_map.remove(&local_ldap_id);
1005                success += 1;
1006                let _ = funs
1007                    .cache()
1008                    .set(
1009                        &funs.conf::<IamConfig>().cache_key_sync_ldap_status,
1010                        &TardisFuns::json.obj_to_string(&IamThirdIntegrationSyncStatusDto { total, success, failed })?,
1011                    )
1012                    .await;
1013                funs.commit().await?;
1014            } else {
1015                total += 1;
1016                funs.begin().await?;
1017                //ldap没有 iam有的 需要同步删除
1018                let delete_result = match sync_config.account_way_to_delete {
1019                    WayToDelete::DoNotDelete => Ok(()),
1020                    WayToDelete::DeleteCert => {
1021                        RbumCertServ::modify_rbum(
1022                            &cert.id,
1023                            &mut RbumCertModifyReq {
1024                                ak: None,
1025                                sk: None,
1026                                sk_invisible: None,
1027
1028                                ignore_check_sk: false,
1029                                ext: None,
1030                                start_time: None,
1031                                end_time: None,
1032                                conn_uri: None,
1033                                status: Some(RbumCertStatusKind::Disabled),
1034                            },
1035                            &funs,
1036                            ctx,
1037                        )
1038                        .await
1039                    }
1040                    WayToDelete::Disable => {
1041                        IamAccountServ::modify_account_agg(
1042                            &cert.rel_rbum_id,
1043                            &IamAccountAggModifyReq {
1044                                disabled: Some(true),
1045                                status: Some(IamAccountStatusKind::Logout),
1046                                logout_type: Some(IamAccountLogoutTypeKind::AutomaticLogout),
1047                                ..Default::default()
1048                            },
1049                            &funs,
1050                            ctx,
1051                        )
1052                        .await
1053                    }
1054                    WayToDelete::DeleteAccount => IamAccountServ::delete_item_with_all_rels(&cert.rel_rbum_id, &funs, ctx).await.map(|_| ()),
1055                };
1056                match delete_result {
1057                    Ok(_) => {
1058                        let _ = IamSearchClient::async_add_or_modify_account_search(&cert.rel_rbum_id, Box::new(true), "", &funs, ctx).await;
1059                        success += 1;
1060                    }
1061                    Err(_) => {
1062                        failed += 1;
1063                    }
1064                }
1065                let _ = funs
1066                    .cache()
1067                    .set(
1068                        &funs.conf::<IamConfig>().cache_key_sync_ldap_status,
1069                        &TardisFuns::json.obj_to_string(&IamThirdIntegrationSyncStatusDto { total, success, failed })?,
1070                    )
1071                    .await;
1072                funs.commit().await?;
1073            };
1074        }
1075        //ldap有的 但是iam没有的 需要添加
1076        for ldap_id in ldap_id_to_account_map.keys() {
1077            let mock_ctx = TardisContext {
1078                owner: TardisFuns::field.nanoid(),
1079                ..ctx.clone()
1080            };
1081            let ldap_resp = ldap_id_to_account_map.get(ldap_id).ok_or_else(|| {
1082                funs.err().not_found(
1083                    "iam_cert_ldap_serv",
1084                    "iam_sync_ldap_user_to_iam",
1085                    "not found account by ldap id",
1086                    "404-iam-cert-conf-not-exist",
1087                )
1088            })?;
1089            let mut funs = iam_constants::get_tardis_inst();
1090            funs.begin().await?;
1091            let add_result = match sync_config.account_way_to_add {
1092                WayToAdd::SynchronizeCert => {
1093                    let result = Self::do_add_account(
1094                        &ldap_resp.account_id,
1095                        &ldap_resp.display_name,
1096                        &ldap_resp.user_name,
1097                        &format!("{}0Pw$", TardisFuns::field.nanoid_len(6)),
1098                        &cert_conf_id,
1099                        &ldap_resp.labor_type,
1100                        RbumCertStatusKind::Enabled,
1101                        &funs,
1102                        &mock_ctx,
1103                    )
1104                    .await;
1105                    if result.is_ok() {
1106                        //添加手机号
1107                        let phone_cert_conf_id = IamCertServ::get_cert_conf_id_by_kind(&IamCertKernelKind::PhoneVCode.to_string(), Some(ctx.own_paths.clone()), &funs).await?;
1108                        if let Err(e) = IamCertPhoneVCodeServ::add_cert_skip_vcode(
1109                            &IamCertPhoneVCodeAddReq {
1110                                phone: TrimString(ldap_resp.mobile.clone()),
1111                            },
1112                            mock_ctx.owner.as_str(),
1113                            phone_cert_conf_id.as_str(),
1114                            &funs,
1115                            ctx,
1116                        )
1117                        .await
1118                        {
1119                            let err_msg = format!("add account:{}add phone phone:{} failed:{}", mock_ctx.owner, ldap_resp.mobile.clone(), e);
1120                            tardis::log::error!("{}", err_msg);
1121                            msg = format!("{msg}{err_msg}\n");
1122                        }
1123
1124                        //添加email
1125                        let email_cert_conf_id = IamCertServ::get_cert_conf_id_by_kind(&IamCertKernelKind::MailVCode.to_string(), Some(ctx.own_paths.clone()), &funs).await?;
1126                        if let Err(e) = IamCertMailVCodeServ::add_cert_skip_activate(
1127                            &IamCertMailVCodeAddReq {
1128                                mail: TrimString(ldap_resp.email.clone()).to_string(),
1129                            },
1130                            mock_ctx.owner.as_str(),
1131                            email_cert_conf_id.as_str(),
1132                            &funs,
1133                            ctx,
1134                        )
1135                        .await
1136                        {
1137                            let err_msg = format!("add account:{} add email:{} failed:{}", mock_ctx.owner, ldap_resp.email.clone(), e);
1138                            tardis::log::error!("{}", err_msg);
1139                            msg = format!("{msg}{err_msg}\n");
1140                        }
1141                    }
1142                    result
1143                }
1144                WayToAdd::NoSynchronizeCert => {
1145                    Self::do_add_account(
1146                        &ldap_resp.account_id,
1147                        &ldap_resp.display_name,
1148                        &ldap_resp.user_name,
1149                        &format!("{}0Pw$", TardisFuns::field.nanoid_len(6)),
1150                        &cert_conf_id,
1151                        &ldap_resp.labor_type,
1152                        RbumCertStatusKind::Disabled,
1153                        &funs,
1154                        &mock_ctx,
1155                    )
1156                    .await
1157                }
1158            };
1159
1160            if let Some(e) = add_result.err() {
1161                let err_msg = format!("add account:{:?} failed:{}", ldap_resp, e);
1162                tardis::log::error!("{}", err_msg);
1163                msg = format!("{msg}{err_msg}\n");
1164                funs.rollback().await?;
1165                failed += 1;
1166                continue;
1167            } else {
1168                success += 1;
1169            }
1170            let _ = funs
1171                .cache()
1172                .set(
1173                    &funs.conf::<IamConfig>().cache_key_sync_ldap_status,
1174                    &TardisFuns::json.obj_to_string(&IamThirdIntegrationSyncStatusDto { total, success, failed })?,
1175                )
1176                .await;
1177            funs.commit().await?;
1178            mock_ctx.execute_task().await?;
1179        }
1180        Ok(msg)
1181    }
1182
1183    pub async fn generate_default_mock_ctx(supplier: &str, tenant_id: Option<String>, funs: &TardisFunsInst) -> TardisContext {
1184        //if tenant_id is some and tenant have cert_conf \
1185        // then assign tenant_id to own_paths
1186        if IamCertServ::get_cert_conf_id_by_kind_supplier(&IamCertExtKind::Ldap.to_string(), supplier, tenant_id.clone(), funs).await.is_ok() {
1187            if let Some(tenant_id) = tenant_id {
1188                return TardisContext {
1189                    own_paths: tenant_id,
1190                    ..Default::default()
1191                };
1192            }
1193        }
1194        TardisContext { ..Default::default() }
1195    }
1196
1197    fn iam_cert_ldap_server_auth_info_to_json(add_req: &IamCertConfLdapAddOrModifyReq) -> TardisResult<String> {
1198        TardisFuns::json.obj_to_string::<IamCertLdapServerAuthInfo>(&(add_req.clone().into()))
1199    }
1200
1201    /// do add account and ldap/userPwd cert \
1202    /// and return account_id
1203    async fn do_add_account(
1204        ldap_id: &str,
1205        account_name: &str,
1206        cert_user_name: &str,
1207        userpwd_password: &str,
1208        ldap_cert_conf_id: &str,
1209        labor_type: &str,
1210        cert_status: RbumCertStatusKind,
1211        funs: &TardisFunsInst,
1212        ctx: &TardisContext,
1213    ) -> TardisResult<String> {
1214        let account_id = IamAccountServ::add_account_agg(
1215            &IamAccountAggAddReq {
1216                id: Some(TrimString(ctx.owner.clone())),
1217                name: TrimString(account_name.to_string()),
1218                cert_user_name: IamCertUserPwdServ::rename_ak_if_duplicate(cert_user_name, funs, ctx).await?,
1219                cert_password: Some(userpwd_password.into()),
1220                cert_phone: None,
1221                cert_mail: None,
1222                role_ids: None,
1223                org_node_ids: None,
1224                scope_level: Some(RbumScopeLevelKind::Root),
1225                disabled: None,
1226                icon: None,
1227                exts: HashMap::new(),
1228                status: Some(RbumCertStatusKind::Pending),
1229                temporary: None,
1230                lock_status: None,
1231                logout_type: None,
1232                labor_type: Some(labor_type.to_string()),
1233            },
1234            false,
1235            funs,
1236            ctx,
1237        )
1238        .await?;
1239        Self::add_or_modify_cert(
1240            &IamCertLdapAddOrModifyReq {
1241                ldap_id: TrimString(ldap_id.to_string()),
1242                status: cert_status,
1243            },
1244            &account_id,
1245            ldap_cert_conf_id,
1246            funs,
1247            ctx,
1248        )
1249        .await?;
1250        IamSearchClient::async_add_or_modify_account_search(&account_id, Box::new(false), "", funs, ctx).await?;
1251        Ok(account_id)
1252    }
1253
1254    async fn get_ldap_client(tenant_id: Option<String>, supplier: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<(LdapClient, IamCertConfLdapResp, String)> {
1255        let cert_conf_id = IamCertServ::get_cert_conf_id_by_kind_supplier(&IamCertExtKind::Ldap.to_string(), supplier, tenant_id, funs).await?;
1256        let cert_conf = Self::get_cert_conf(&cert_conf_id, funs, ctx).await?;
1257        let client = LdapClient::new(&cert_conf.conn_uri, cert_conf.port, cert_conf.is_tls, cert_conf.timeout, &cert_conf.base_dn).await?;
1258        Ok((client, cert_conf, cert_conf_id))
1259    }
1260
1261    async fn get_ldap_cert_account_by_account(account_id: &str, rel_rbum_cert_conf_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<Vec<RbumCertSummaryResp>> {
1262        RbumCertServ::find_rbums(
1263            &RbumCertFilterReq {
1264                rel_rbum_cert_conf_ids: Some(vec![rel_rbum_cert_conf_id.to_string()]),
1265                rel_rbum_id: Some(account_id.to_string()),
1266                ..Default::default()
1267            },
1268            None,
1269            None,
1270            funs,
1271            ctx,
1272        )
1273        .await
1274    }
1275
1276    pub async fn get_ldap_resp_by_cn(cn: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<Vec<IamAccountExtSysResp>> {
1277        let (mut ldap_client, cert_conf, cert_conf_id) = Self::get_ldap_client(Some(ctx.own_paths.clone()), "", funs, ctx).await?;
1278        if ldap_client.bind_by_dn(&cert_conf.principal, &cert_conf.credentials).await?.is_none() {
1279            ldap_client.unbind().await?;
1280            return Err(funs.err().unauthorized("ldap_cert_conf", "add", "validation error", "401-rbum-cert-valid-error"));
1281        }
1282        let ldap_accounts: Vec<IamAccountExtSysResp> = ldap_client
1283            .search(&format!("cn={}", cn), &cert_conf.package_account_return_attr_with(vec!["dn", "cn"]))
1284            .await?
1285            .into_iter()
1286            .map(|r| IamAccountExtSysResp::form_ldap_search_resp(r, &cert_conf))
1287            .collect();
1288        let mut result = vec![];
1289        for account in &ldap_accounts {
1290            let mut ldap_account = account.clone();
1291            if let Some(account_id) = Self::get_cert_rel_account_by_dn(&account.account_id, &cert_conf_id, funs, ctx).await? {
1292                ldap_account.account_id = account_id;
1293            }
1294            result.push(ldap_account);
1295        }
1296        Ok(result)
1297    }
1298    ///# Examples
1299    ///
1300    ///```
1301    ///  use bios_iam::basic::serv::iam_cert_ldap_serv::IamCertLdapServ;
1302    ///  assert_eq!(IamCertLdapServ::dn_to_cn("cn=admin,ou=x,dc=x,dc=x"), "admin".to_string());
1303    ///  assert_eq!(IamCertLdapServ::dn_to_cn("ou=x,dc=x,dc=x"), "ou=x,dc=x,dc=x".to_string());
1304    ///  assert_eq!(IamCertLdapServ::dn_to_cn("cn=,ou=x,dc=x,dc=x"), "".to_string());
1305    ///  assert_eq!(IamCertLdapServ::dn_to_cn("hello world"), "hello world".to_string());
1306    /// ```
1307    pub fn dn_to_cn(dn: &str) -> String {
1308        let dn_regex = Regex::new(r"(,|^)[cC][nN]=(.+?)(,|$)").expect("Regular parsing error");
1309        let cn = if dn_regex.is_match(dn) {
1310            let int = dn.find("cn=").unwrap_or_default();
1311            let a = &dn[int + 3..];
1312            let int = a.find(',').unwrap_or_default();
1313            &a[..int]
1314        } else {
1315            warn!("dn:{} is not match regex!", dn);
1316            dn
1317        };
1318        cn.to_string()
1319    }
1320}
1321
1322pub(crate) mod ldap {
1323    use std::collections::HashMap;
1324    use std::time::Duration;
1325
1326    use ldap3::adapters::{Adapter, EntriesOnly, PagedResults};
1327    use ldap3::{log::warn, Ldap, LdapConnAsync, LdapConnSettings, Scope, SearchEntry};
1328    use serde::{Deserialize, Serialize};
1329
1330    use tardis::basic::{error::TardisError, result::TardisResult};
1331    use tardis::log::trace;
1332
1333    pub struct LdapClient {
1334        ldap: Ldap,
1335        base_dn: String,
1336    }
1337
1338    impl LdapClient {
1339        pub async fn new(url: &str, port: u16, tls: bool, time_out: u64, base_dn: &str) -> TardisResult<LdapClient> {
1340            let mut setting = if tls {
1341                LdapConnSettings::new().set_starttls(true).set_no_tls_verify(true)
1342            } else {
1343                LdapConnSettings::new()
1344            };
1345            setting = setting.set_conn_timeout(Duration::from_secs(time_out));
1346            let url = if &url[url.len() - 1..] == "/" {
1347                format!("{}:{port}", &url[..url.len() - 1])
1348            } else {
1349                format!("{url}:{port}")
1350            };
1351            let (conn, ldap) = LdapConnAsync::with_settings(setting, &url)
1352                .await
1353                .map_err(|e| TardisError::internal_error(&format!("[Iam.Ldap] connection error: {e:?}"), "500-iam-connection-error"))?;
1354            ldap3::drive!(conn);
1355            Ok(LdapClient {
1356                ldap,
1357                base_dn: base_dn.to_string(),
1358            })
1359        }
1360
1361        pub async fn bind(&mut self, cn: &str, pw: &str) -> TardisResult<Option<String>> {
1362            let dn = format!("cn={},{}", cn, self.base_dn);
1363            self.bind_by_dn(&dn, pw).await
1364        }
1365
1366        pub async fn bind_by_dn(&mut self, dn: &str, pw: &str) -> TardisResult<Option<String>> {
1367            trace!("[Iam.Ldap] bind_by_dn dn:{dn}");
1368            let result = self
1369                .ldap
1370                .simple_bind(dn, pw)
1371                .await
1372                .map_err(|e| TardisError::internal_error(&format!("[Iam.Ldap] bind error: {e:?}"), "500-iam-ldap-bind-error"))?
1373                .success()
1374                .map(|_| ());
1375            if let Some(err) = result.err() {
1376                warn!("[Iam.Ldap] ldap bind error: {:?}", err);
1377                Ok(None)
1378            } else {
1379                Ok(Some(dn.to_string()))
1380            }
1381        }
1382
1383        pub async fn search(&mut self, filter: &str, return_attr: &Vec<&str>) -> TardisResult<Vec<LdapSearchResp>> {
1384            trace!("[Iam.Ldap] search filter: {filter} base_dn: {}", self.base_dn);
1385            let (rs, _) = self
1386                .ldap
1387                .search(&self.base_dn, Scope::Subtree, filter, return_attr)
1388                .await
1389                .map_err(|e| TardisError::internal_error(&format!("[Iam.Ldap] search error: {e:?}"), "500-iam-ldap-search-error"))?
1390                .success()
1391                .map_err(|e| TardisError::internal_error(&format!("[Iam.Ldap] search error: {e:?}"), "500-iam-ldap-search-result-error"))?;
1392            let result = rs.into_iter().map(SearchEntry::construct).map(|r| LdapSearchResp { dn: r.dn, attrs: r.attrs }).collect();
1393            Ok(result)
1394        }
1395
1396        pub async fn page_search(&mut self, page_size: i32, filter: &str, return_attr: &Vec<&str>) -> TardisResult<Vec<LdapSearchResp>> {
1397            trace!("[Iam.Ldap] page_search filter: {filter} base_dn: {}", self.base_dn);
1398            let adapters: Vec<Box<dyn Adapter<_, _>>> = vec![Box::new(EntriesOnly::new()), Box::new(PagedResults::new(page_size))];
1399            let mut search = self
1400                .ldap
1401                .streaming_search_with(adapters, &self.base_dn, Scope::Subtree, filter, return_attr)
1402                .await
1403                .map_err(|e| TardisError::internal_error(&format!("[Iam.Ldap] page_search error: {e:?}"), "500-iam-ldap-search-error"))?;
1404            let mut result = vec![];
1405            while let Some(entry) =
1406                search.next().await.map_err(|e| TardisError::internal_error(&format!("[Iam.Ldap] page_search next() error: {e:?}"), "500-iam-ldap-search-result-error"))?
1407            {
1408                let entry = SearchEntry::construct(entry);
1409                result.push(entry.clone());
1410            }
1411            let result = result.into_iter().map(|r| LdapSearchResp { dn: r.dn, attrs: r.attrs }).collect();
1412            Ok(result)
1413        }
1414
1415        /// only used for once
1416        pub fn with_limit(&mut self, limit: i32) -> TardisResult<()> {
1417            self.ldap.with_search_options(self.ldap.search_opts.clone().unwrap_or_default().sizelimit(limit));
1418            Ok(())
1419        }
1420
1421        pub async fn get_by_dn(&mut self, dn: &str, return_attr: &Vec<&str>) -> TardisResult<Option<LdapSearchResp>> {
1422            let (rs, _) = self
1423                .ldap
1424                .search(dn, Scope::Subtree, "objectClass=*", return_attr)
1425                .await
1426                .map_err(|e| TardisError::internal_error(&format!("[Iam.Ldap] search error: {e:?}"), "500-iam-ldap-search-error"))?
1427                .success()
1428                .map_err(|e| TardisError::internal_error(&format!("[Iam.Ldap] search error: {e:?}"), "500-iam-ldap-search-result-error"))?;
1429            let result = rs.into_iter().map(SearchEntry::construct).map(|r| LdapSearchResp { dn: r.dn, attrs: r.attrs }).collect::<Vec<LdapSearchResp>>();
1430            if let Some(result) = result.first() {
1431                Ok(Some(result.clone()))
1432            } else {
1433                Ok(None)
1434            }
1435        }
1436
1437        pub async fn unbind(&mut self) -> TardisResult<()> {
1438            self.ldap.unbind().await.map_err(|e| TardisError::internal_error(&format!("[Iam.Ldap] unbind error: {e:?}"), "500-iam-ldap-unbind-error"))
1439        }
1440    }
1441
1442    #[derive(Serialize, Deserialize, Clone, Debug)]
1443    pub struct LdapSearchResp {
1444        pub dn: String,
1445        pub attrs: HashMap<String, Vec<String>>,
1446    }
1447
1448    impl LdapSearchResp {
1449        pub fn get_simple_attr(&self, attr_name: &str) -> Option<String> {
1450            if let Some(values) = self.attrs.get(attr_name) {
1451                if let Some(value) = values.first() {
1452                    return Some(value.to_string());
1453                }
1454            }
1455            None
1456        }
1457    }
1458}
1459
1460#[cfg(test)]
1461mod tests {
1462    use tardis::basic::result::TardisResult;
1463
1464    use super::ldap::LdapClient;
1465    use tardis::tokio;
1466
1467    const LDAP_URL: &str = "ldap://x.x.x.x";
1468    const LDAP_PORT: u16 = 389;
1469    const LDAP_TLS: bool = false;
1470    const LDAP_BASE_DN: &str = "ou=x,dc=x,dc=x";
1471    const LDAP_USER_DN: &str = "cn=admin,ou=x,dc=x,dc=x";
1472    const LDAP_USER: &str = "admin";
1473    const LDAP_PW: &str = "123456";
1474    const LDAP_TIME_OUT: u64 = 5;
1475
1476    #[tokio::test]
1477    #[ignore]
1478    async fn bind_by_dn() -> TardisResult<()> {
1479        let mut ldap = LdapClient::new(LDAP_URL, LDAP_PORT, LDAP_TLS, LDAP_TIME_OUT, LDAP_BASE_DN).await?;
1480        let result = ldap.bind_by_dn(LDAP_USER_DN, LDAP_PW).await?;
1481        assert!(result.is_some());
1482        Ok(())
1483    }
1484
1485    #[tokio::test]
1486    #[ignore]
1487    async fn bind() -> TardisResult<()> {
1488        let mut ldap = LdapClient::new(LDAP_URL, LDAP_PORT, LDAP_TLS, LDAP_TIME_OUT, LDAP_BASE_DN).await?;
1489        let result = ldap.bind(LDAP_USER, LDAP_PW).await?;
1490        assert!(result.is_some());
1491        Ok(())
1492    }
1493
1494    #[tokio::test]
1495    #[ignore]
1496    async fn search() -> TardisResult<()> {
1497        let mut ldap = LdapClient::new(LDAP_URL, LDAP_PORT, LDAP_TLS, LDAP_TIME_OUT, LDAP_BASE_DN).await?;
1498        ldap.bind(LDAP_USER, LDAP_PW).await?;
1499        let _result = ldap.search("(&(objectClass=inetOrgPerson)(cn=*130*))", &vec!["dn", "cn", "displayName"]).await?;
1500        // assert_eq!(result.len(), 1);
1501        Ok(())
1502    }
1503
1504    #[tokio::test]
1505    #[ignore]
1506    async fn page_search() -> TardisResult<()> {
1507        let mut ldap = LdapClient::new(LDAP_URL, LDAP_PORT, LDAP_TLS, LDAP_TIME_OUT, LDAP_BASE_DN).await?;
1508        ldap.bind(LDAP_USER, LDAP_PW).await?;
1509        let _result = ldap.page_search(50, "objectClass=inetOrgPerson", &vec!["dn", "cn", "displayName"]).await?;
1510        // assert_eq!(result.len(), 1);
1511        Ok(())
1512    }
1513
1514    #[tokio::test]
1515    #[ignore]
1516    async fn page_search_with_limit() -> TardisResult<()> {
1517        let mut ldap = LdapClient::new(LDAP_URL, LDAP_PORT, LDAP_TLS, LDAP_TIME_OUT, LDAP_BASE_DN).await?;
1518        ldap.bind(LDAP_USER, LDAP_PW).await?;
1519        ldap.with_limit(1)?;
1520        let result = ldap.page_search(50, "objectClass=inetOrgPerson", &vec!["dn", "cn", "displayName"]).await?;
1521        assert_eq!(result.len(), 1);
1522        let _result = ldap.page_search(50, "objectClass=person", &vec!["dn", "cn", "displayName"]).await?;
1523        // assert_eq!(result.len(), 1);
1524        Ok(())
1525    }
1526
1527    #[tokio::test]
1528    #[ignore]
1529    async fn unbind() -> TardisResult<()> {
1530        let mut ldap = LdapClient::new(LDAP_URL, LDAP_PORT, LDAP_TLS, LDAP_TIME_OUT, LDAP_BASE_DN).await?;
1531        ldap.bind(LDAP_USER, LDAP_PW).await?;
1532        ldap.unbind().await?;
1533        Ok(())
1534    }
1535}
1536
1537#[derive(Serialize, Deserialize)]
1538struct IamCertLdapServerAuthInfo {
1539    // server_uri is in RbumCertConf's conn_uri
1540    pub port: u16,
1541    pub is_tls: bool,
1542    // sec
1543    pub timeout: u64,
1544    // such as: "cn=ldap_server,cn=ldap_servers,cn=config"
1545    pub principal: String,
1546    pub credentials: String,
1547    pub base_dn: String,
1548
1549    pub account_unique_id: String,
1550    pub account_field_map: AccountFieldMap,
1551    // pub org_unique_id: String,
1552    // pub org_field_map: OrgFieldMap,
1553}
1554
1555impl From<IamCertConfLdapAddOrModifyReq> for IamCertLdapServerAuthInfo {
1556    fn from(v: IamCertConfLdapAddOrModifyReq) -> Self {
1557        IamCertLdapServerAuthInfo {
1558            port: v.port.unwrap_or(if v.is_tls { 636 } else { 389 }),
1559            is_tls: v.is_tls,
1560            timeout: v.timeout.unwrap_or(5),
1561            principal: v.principal.to_string(),
1562            credentials: v.credentials.to_string(),
1563            base_dn: v.base_dn.to_string(),
1564            account_unique_id: v.account_unique_id.clone(),
1565            account_field_map: v.account_field_map,
1566            // org_unique_id: v.org_unique_id.clone(),
1567            // org_field_map: v.org_field_map,
1568        }
1569    }
1570}
1571
1572#[derive(poem_openapi::Object, Serialize, Deserialize, Debug, Clone)]
1573pub struct AccountFieldMap {
1574    // The base condition fragment of the search filter,
1575    // without the outermost parentheses.
1576    // For example, the complete search filter is: (&(objectCategory=group)(|(cn=Test*)(cn=Admin*))),
1577    // this field can be &(objectCategory=group)
1578    // default : objectClass=person
1579    pub search_base_filter: Option<String>,
1580    pub field_user_name: String,
1581    pub field_display_name: String,
1582    pub field_mobile: String,
1583    pub field_email: String,
1584    pub field_labor_type: String,
1585    pub field_labor_type_map: Option<HashMap<String, String>>,
1586
1587    pub field_user_name_remarks: String,
1588    pub field_display_name_remarks: String,
1589    pub field_mobile_remarks: String,
1590    pub field_email_remarks: String,
1591    pub field_labor_type_remarks: String,
1592}
1593
1594#[derive(poem_openapi::Object, Serialize, Deserialize, Debug, Clone)]
1595pub struct OrgFieldMap {
1596    pub search_base_filter: Option<String>,
1597    pub field_dept_id: String,
1598    pub field_dept_name: String,
1599    pub field_parent_dept_id: String,
1600
1601    pub field_dept_id_remarks: String,
1602    pub field_dept_name_remarks: String,
1603    pub field_parent_dept_id_remarks: String,
1604}