LdapSecuritySettingContents.jsx 19 KB

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