LdapSecuritySetting.jsx 19 KB

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