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 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 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 })?;
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 })
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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub port: u16,
1541 pub is_tls: bool,
1542 pub timeout: u64,
1544 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 }
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 }
1569 }
1570}
1571
1572#[derive(poem_openapi::Object, Serialize, Deserialize, Debug, Clone)]
1573pub struct AccountFieldMap {
1574 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}