LdapSecuritySettingContents.jsx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. import React from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import PropTypes from 'prop-types';
  4. import AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
  5. import AdminLdapSecurityContainer from '~/client/services/AdminLdapSecurityContainer';
  6. import { toastSuccess, toastError } from '~/client/util/toastr';
  7. import { withUnstatedContainers } from '../../UnstatedUtils';
  8. import LdapAuthTestModal from './LdapAuthTestModal';
  9. class LdapSecuritySettingContents extends React.Component {
  10. constructor(props) {
  11. super(props);
  12. this.state = {
  13. isLdapAuthTestModalShown: false,
  14. };
  15. this.onClickSubmit = this.onClickSubmit.bind(this);
  16. this.openLdapAuthTestModal = this.openLdapAuthTestModal.bind(this);
  17. this.closeLdapAuthTestModal = this.closeLdapAuthTestModal.bind(this);
  18. }
  19. async onClickSubmit() {
  20. const { t, adminLdapSecurityContainer, adminGeneralSecurityContainer } = this.props;
  21. try {
  22. await adminLdapSecurityContainer.updateLdapSetting();
  23. await adminGeneralSecurityContainer.retrieveSetupStratedies();
  24. toastSuccess(t('security_settings.ldap.updated_ldap'));
  25. }
  26. catch (err) {
  27. toastError(err);
  28. }
  29. }
  30. openLdapAuthTestModal() {
  31. this.setState({ isLdapAuthTestModalShown: true });
  32. }
  33. closeLdapAuthTestModal() {
  34. this.setState({ isLdapAuthTestModalShown: false });
  35. }
  36. render() {
  37. const { t, adminGeneralSecurityContainer, adminLdapSecurityContainer } = this.props;
  38. const { isLdapEnabled } = adminGeneralSecurityContainer.state;
  39. return (
  40. <React.Fragment>
  41. <h2 className="alert-anchor border-bottom">
  42. LDAP
  43. </h2>
  44. <div className="row">
  45. <div className="col-6 offset-3">
  46. <div className="form-check form-switch form-check-success">
  47. <input
  48. id="isLdapEnabled"
  49. className="form-check-input"
  50. type="checkbox"
  51. checked={isLdapEnabled}
  52. onChange={() => { adminGeneralSecurityContainer.switchIsLdapEnabled() }}
  53. />
  54. <label className="form-label form-check-label" htmlFor="isLdapEnabled">
  55. {t('security_settings.ldap.enable_ldap')}
  56. </label>
  57. </div>
  58. {(!adminGeneralSecurityContainer.state.setupStrategies.includes('ldap') && isLdapEnabled)
  59. && <div className="badge bg-warning text-dark">{t('security_settings.setup_is_not_yet_complete')}</div>}
  60. </div>
  61. </div>
  62. {isLdapEnabled && (
  63. <React.Fragment>
  64. <h3 className="border-bottom">{t('security_settings.configuration')}</h3>
  65. <div className="row">
  66. <label htmlFor="serverUrl" className="text-start text-md-end col-md-3 col-form-label">
  67. Server URL
  68. </label>
  69. <div className="col-md-6">
  70. <input
  71. className="form-control"
  72. type="text"
  73. name="serverUrl"
  74. defaultValue={adminLdapSecurityContainer.state.serverUrl || ''}
  75. onChange={e => adminLdapSecurityContainer.changeServerUrl(e.target.value)}
  76. />
  77. <small>
  78. <p
  79. className="form-text text-muted"
  80. // eslint-disable-next-line react/no-danger
  81. dangerouslySetInnerHTML={{ __html: t('security_settings.ldap.server_url_detail') }}
  82. />
  83. {t('security_settings.example')}: <code>ldaps://ldap.company.com/ou=people,dc=company,dc=com</code>
  84. </small>
  85. </div>
  86. </div>
  87. <div className="row">
  88. <label className="form-label text-start text-md-end col-md-3 col-form-label">
  89. <strong>{t('security_settings.ldap.bind_mode')}</strong>
  90. </label>
  91. <div className="col-md-6">
  92. <div className="dropdown">
  93. <button
  94. className="btn btn-outline-secondary dropdown-toggle"
  95. type="button"
  96. id="dropdownMenuButton"
  97. data-bs-toggle="dropdown"
  98. aria-haspopup="true"
  99. aria-expanded="true"
  100. >
  101. {adminLdapSecurityContainer.state.isUserBind
  102. ? <span className="pull-left">{t('security_settings.ldap.bind_user')}</span>
  103. : <span className="pull-left">{t('security_settings.ldap.bind_manager')}</span>}
  104. </button>
  105. <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
  106. <button className="dropdown-item" type="button" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(true) }}>
  107. {t('security_settings.ldap.bind_user')}
  108. </button>
  109. <button className="dropdown-item" type="button" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(false) }}>
  110. {t('security_settings.ldap.bind_manager')}
  111. </button>
  112. </div>
  113. </div>
  114. </div>
  115. </div>
  116. <div className="row">
  117. <label className="form-label text-start text-md-end col-md-3 col-form-label">
  118. <strong>Bind DN</strong>
  119. </label>
  120. <div className="col-md-6">
  121. <input
  122. className="form-control"
  123. type="text"
  124. name="bindDN"
  125. defaultValue={adminLdapSecurityContainer.state.ldapBindDN || ''}
  126. onChange={e => adminLdapSecurityContainer.changeBindDN(e.target.value)}
  127. />
  128. {(adminLdapSecurityContainer.state.isUserBind === true) ? (
  129. <p className="form-text text-muted passport-ldap-userbind">
  130. <small>
  131. {t('security_settings.ldap.bind_DN_user_detail1')}<br />
  132. {/* eslint-disable-next-line react/no-danger */}
  133. <span dangerouslySetInnerHTML={{ __html: t('security_settings.ldap.bind_DN_user_detail2') }} /><br />
  134. {t('security_settings.example')}1: <code>uid={'{{ username }}'},dc=domain,dc=com</code><br />
  135. {t('security_settings.example')}2: <code>{'{{ username }}'}@domain.com</code>
  136. </small>
  137. </p>
  138. )
  139. : (
  140. <p className="form-text text-muted passport-ldap-managerbind">
  141. <small>
  142. {t('security_settings.ldap.bind_DN_manager_detail')}<br />
  143. {t('security_settings.example')}1: <code>uid=admin,dc=domain,dc=com</code><br />
  144. {t('security_settings.example')}2: <code>admin@domain.com</code>
  145. </small>
  146. </p>
  147. )}
  148. </div>
  149. </div>
  150. <div className="row">
  151. <div htmlFor="bindDNPassword" className="text-start text-md-end col-md-3 col-form-label">
  152. <strong>{t('security_settings.ldap.bind_DN_password')}</strong>
  153. </div>
  154. <div className="col-md-6">
  155. {(adminLdapSecurityContainer.state.isUserBind) ? (
  156. <p className="card custom-card passport-ldap-userbind">
  157. <small>
  158. {t('security_settings.ldap.bind_DN_password_user_detail')}
  159. </small>
  160. </p>
  161. )
  162. : (
  163. <>
  164. <p className="card custom-card passport-ldap-managerbind">
  165. <small>
  166. {t('security_settings.ldap.bind_DN_password_manager_detail')}
  167. </small>
  168. </p>
  169. <input
  170. className="form-control passport-ldap-managerbind"
  171. type="password"
  172. name="bindDNPassword"
  173. defaultValue={adminLdapSecurityContainer.state.ldapBindDNPassword || ''}
  174. onChange={e => adminLdapSecurityContainer.changeBindDNPassword(e.target.value)}
  175. />
  176. </>
  177. )}
  178. </div>
  179. </div>
  180. <div className="row">
  181. <label className="form-label text-start text-md-end col-md-3 col-form-label">
  182. <strong>{t('security_settings.ldap.search_filter')}</strong>
  183. </label>
  184. <div className="col-md-6">
  185. <input
  186. className="form-control"
  187. type="text"
  188. name="searchFilter"
  189. defaultValue={adminLdapSecurityContainer.state.ldapSearchFilter || ''}
  190. onChange={e => adminLdapSecurityContainer.changeSearchFilter(e.target.value)}
  191. />
  192. <p className="form-text text-muted">
  193. <small>
  194. {t('security_settings.ldap.search_filter_detail1')}<br />
  195. {/* eslint-disable-next-line react/no-danger */}
  196. <span dangerouslySetInnerHTML={{ __html: t('security_settings.ldap.search_filter_detail2') }} /><br />
  197. {/* eslint-disable-next-line react/no-danger */}
  198. <span dangerouslySetInnerHTML={{ __html: t('security_settings.ldap.search_filter_detail3') }} />
  199. </small>
  200. </p>
  201. <p className="form-text text-muted">
  202. <small>
  203. {t('security_settings.example')}1 - {t('security_settings.ldap.search_filter_example1')}:
  204. <code>(|(uid={'{{username}}'})(mail={'{{username}}'}))</code><br />
  205. {t('security_settings.example')}2 - {t('security_settings.ldap.search_filter_example2')}:
  206. <code>(sAMAccountName={'{{username}}'})</code>
  207. </small>
  208. </p>
  209. </div>
  210. </div>
  211. <h3 className="alert-anchor border-bottom">
  212. Attribute Mapping ({t('security_settings.optional')})
  213. </h3>
  214. <div className="row">
  215. <label className="form-label text-start text-md-end col-md-3 col-form-label">
  216. <strong htmlFor="attrMapUsername">{t('username')}</strong>
  217. </label>
  218. <div className="col-md-6">
  219. <input
  220. className="form-control"
  221. type="text"
  222. placeholder="Default: uid"
  223. name="attrMapUsername"
  224. defaultValue={adminLdapSecurityContainer.state.ldapAttrMapUsername || ''}
  225. onChange={e => adminLdapSecurityContainer.changeAttrMapUsername(e.target.value)}
  226. />
  227. <p className="form-text text-muted">
  228. {/* eslint-disable-next-line react/no-danger */}
  229. <small dangerouslySetInnerHTML={{ __html: t('security_settings.ldap.username_detail') }} />
  230. </p>
  231. </div>
  232. </div>
  233. <div className="row">
  234. <div className="offset-md-3 col-md-6">
  235. <div className="form-check form-check-success">
  236. <input
  237. type="checkbox"
  238. className="form-check-input"
  239. id="isSameUsernameTreatedAsIdenticalUser"
  240. checked={adminLdapSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser}
  241. onChange={() => { adminLdapSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
  242. />
  243. <label
  244. className="form-check-label"
  245. htmlFor="isSameUsernameTreatedAsIdenticalUser"
  246. // eslint-disable-next-line react/no-danger
  247. dangerouslySetInnerHTML={{ __html: t('security_settings.Treat username matching as identical') }}
  248. />
  249. </div>
  250. <p className="form-text text-muted">
  251. {/* eslint-disable-next-line react/no-danger */}
  252. <small dangerouslySetInnerHTML={{ __html: t('security_settings.Treat username matching as identical_warn') }} />
  253. </p>
  254. </div>
  255. </div>
  256. <div className="row">
  257. <label className="form-label text-start text-md-end col-md-3 col-form-label">
  258. <strong htmlFor="attrMapMail">{t('Email')}</strong>
  259. </label>
  260. <div className="col-md-6">
  261. <input
  262. className="form-control"
  263. type="text"
  264. placeholder="Default: mail"
  265. name="attrMapMail"
  266. defaultValue={adminLdapSecurityContainer.state.ldapAttrMapMail || ''}
  267. onChange={e => adminLdapSecurityContainer.changeAttrMapMail(e.target.value)}
  268. />
  269. <p className="form-text text-muted">
  270. <small>
  271. {t('security_settings.ldap.mail_detail')}
  272. </small>
  273. </p>
  274. </div>
  275. </div>
  276. <div className="row">
  277. <label className="form-label text-start text-md-end col-md-3 col-form-label">
  278. <strong htmlFor="attrMapName">{t('Name')}</strong>
  279. </label>
  280. <div className="col-md-6">
  281. <input
  282. className="form-control"
  283. type="text"
  284. name="attrMapName"
  285. defaultValue={adminLdapSecurityContainer.state.ldapAttrMapName || ''}
  286. onChange={e => adminLdapSecurityContainer.changeAttrMapName(e.target.value)}
  287. />
  288. <p className="form-text text-muted">
  289. <small>
  290. {t('security_settings.ldap.name_detail')}
  291. </small>
  292. </p>
  293. </div>
  294. </div>
  295. <h3 className="alert-anchor border-bottom">
  296. {t('security_settings.ldap.group_search_filter')} ({t('security_settings.optional')})
  297. </h3>
  298. <div className="row">
  299. <label className="form-label text-start text-md-end col-md-3 col-form-label">
  300. <strong htmlFor="groupSearchBase">{t('security_settings.ldap.group_search_base_DN')}</strong>
  301. </label>
  302. <div className="col-md-6">
  303. <input
  304. className="form-control"
  305. type="text"
  306. name="groupSearchBase"
  307. defaultValue={adminLdapSecurityContainer.state.ldapGroupSearchBase || ''}
  308. onChange={e => adminLdapSecurityContainer.changeGroupSearchBase(e.target.value)}
  309. />
  310. <p className="form-text text-muted">
  311. <small>
  312. {/* eslint-disable-next-line react/no-danger */}
  313. <span dangerouslySetInnerHTML={{ __html: t('security_settings.ldap.group_search_base_DN_detail') }} /><br />
  314. {t('security_settings.example')}: <code>ou=groups,dc=domain,dc=com</code>
  315. </small>
  316. </p>
  317. </div>
  318. </div>
  319. <div className="row">
  320. <label className="form-label text-start text-md-end col-md-3 col-form-label">
  321. <strong htmlFor="groupSearchFilter">{t('security_settings.ldap.group_search_filter')}</strong>
  322. </label>
  323. <div className="col-md-6">
  324. <input
  325. className="form-control"
  326. type="text"
  327. name="groupSearchFilter"
  328. defaultValue={adminLdapSecurityContainer.state.ldapGroupSearchFilter || ''}
  329. onChange={e => adminLdapSecurityContainer.changeGroupSearchFilter(e.target.value)}
  330. />
  331. <p className="form-text text-muted">
  332. <small>
  333. {/* eslint-disable react/no-danger */}
  334. <span dangerouslySetInnerHTML={{ __html: t('security_settings.ldap.group_search_filter_detail1') }} /><br />
  335. <span dangerouslySetInnerHTML={{ __html: t('security_settings.ldap.group_search_filter_detail2') }} /><br />
  336. <span dangerouslySetInnerHTML={{ __html: t('security_settings.ldap.group_search_filter_detail3') }} />
  337. {/* eslint-enable react/no-danger */}
  338. </small>
  339. </p>
  340. <p className="form-text text-muted">
  341. <small>
  342. {t('security_settings.example')}:
  343. {/* eslint-disable-next-line react/no-danger */}
  344. <span dangerouslySetInnerHTML={{ __html: t('security_settings.ldap.group_search_filter_detail4') }} />
  345. </small>
  346. </p>
  347. </div>
  348. </div>
  349. <div className="row">
  350. <label className="form-label text-start text-md-end col-md-3 col-form-label">
  351. <strong htmlFor="groupDnProperty">{t('security_settings.ldap.group_search_user_DN_property')}</strong>
  352. </label>
  353. <div className="col-md-6">
  354. <input
  355. className="form-control"
  356. type="text"
  357. placeholder="Default: uid"
  358. name="groupDnProperty"
  359. defaultValue={adminLdapSecurityContainer.state.ldapGroupDnProperty || ''}
  360. onChange={e => adminLdapSecurityContainer.changeGroupDnProperty(e.target.value)}
  361. />
  362. <p className="form-text text-muted">
  363. {/* eslint-disable-next-line react/no-danger */}
  364. <small dangerouslySetInnerHTML={{ __html: t('security_settings.ldap.group_search_user_DN_property_detail') }} />
  365. </p>
  366. </div>
  367. </div>
  368. <div className="row my-3">
  369. <div className="offset-3 col-5">
  370. <button
  371. type="button"
  372. className="btn btn-primary"
  373. disabled={adminLdapSecurityContainer.state.retrieveError != null}
  374. onClick={this.onClickSubmit}
  375. >
  376. {t('Update')}
  377. </button>
  378. <button
  379. type="button"
  380. className="btn btn-outline-secondary ms-2"
  381. onClick={this.openLdapAuthTestModal}
  382. >{t('security_settings.ldap.test_config')}
  383. </button>
  384. </div>
  385. </div>
  386. </React.Fragment>
  387. )}
  388. <LdapAuthTestModal isOpen={this.state.isLdapAuthTestModalShown} onClose={this.closeLdapAuthTestModal} />
  389. </React.Fragment>
  390. );
  391. }
  392. }
  393. LdapSecuritySettingContents.propTypes = {
  394. t: PropTypes.func.isRequired, // i18next
  395. adminGeneralSecurityContainer: PropTypes.instanceOf(AdminGeneralSecurityContainer).isRequired,
  396. adminLdapSecurityContainer: PropTypes.instanceOf(AdminLdapSecurityContainer).isRequired,
  397. };
  398. const LdapSecuritySettingContentsWrapperFC = (props) => {
  399. const { t } = useTranslation('admin');
  400. return <LdapSecuritySettingContents t={t} {...props} />;
  401. };
  402. const LdapSecuritySettingContentsWrapper = withUnstatedContainers(LdapSecuritySettingContentsWrapperFC, [
  403. AdminGeneralSecurityContainer,
  404. AdminLdapSecurityContainer,
  405. ]);
  406. export default LdapSecuritySettingContentsWrapper;