LdapSecuritySetting.jsx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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-xs-3 my-3 text-right">
  61. <strong>Use LDAP</strong>
  62. </div>
  63. <div className="col-xs-6 text-left">
  64. <div className="checkbox checkbox-success">
  65. <input
  66. id="isLdapEnabled"
  67. type="checkbox"
  68. checked={isLdapEnabled}
  69. onChange={() => { adminGeneralSecurityContainer.switchIsLdapEnabled() }}
  70. />
  71. <label htmlFor="isLdapEnabled">
  72. {t('security_setting.ldap.enable_ldap')}
  73. </label>
  74. </div>
  75. {(!adminGeneralSecurityContainer.state.setupStrategies.includes('ldap') && isLdapEnabled)
  76. && <div className="label label-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
  77. </div>
  78. </div>
  79. {isLdapEnabled && (
  80. <React.Fragment>
  81. <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
  82. <div className="row mb-5">
  83. <label htmlFor="serverUrl" className="col-xs-3 control-label text-right">Server URL</label>
  84. <div className="col-xs-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. <strong className="col-xs-3 text-right">{t('security_setting.ldap.bind_mode')}</strong>
  104. <div className="col-xs-6 text-left">
  105. <div className="my-0 btn-group">
  106. <div className="dropdown">
  107. <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
  108. {adminLdapSecurityContainer.state.isUserBind
  109. ? <span className="pull-left">{t('security_setting.ldap.bind_user')}</span>
  110. : <span className="pull-left">{t('security_setting.ldap.bind_manager')}</span>}
  111. <span className="bs-caret pull-right">
  112. <span className="caret" />
  113. </span>
  114. </button>
  115. {/* TODO adjust dropdown after BS4 */}
  116. <ul className="dropdown-menu" role="menu">
  117. <li key="user" role="presentation" type="button" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(true) }}>
  118. <a role="menuitem">{t('security_setting.ldap.bind_user')}</a>
  119. </li>
  120. <li key="manager" role="presentation" type="button" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode(false) }}>
  121. <a role="menuitem">{t('security_setting.ldap.bind_manager')}</a>
  122. </li>
  123. </ul>
  124. </div>
  125. </div>
  126. </div>
  127. </div>
  128. <div className="row mb-5">
  129. <strong className="col-xs-3 text-right">Bind DN</strong>
  130. <div className="col-xs-6">
  131. <input
  132. className="form-control"
  133. type="text"
  134. name="bindDN"
  135. defaultValue={adminLdapSecurityContainer.state.ldapBindDN || ''}
  136. onChange={e => adminLdapSecurityContainer.changeBindDN(e.target.value)}
  137. />
  138. {(adminLdapSecurityContainer.state.isUserBind === true) ? (
  139. <p className="help-block passport-ldap-userbind">
  140. <small>
  141. {t('security_setting.ldap.bind_DN_user_detail1')}<br />
  142. {/* eslint-disable-next-line react/no-danger */}
  143. <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.bind_DN_user_detail2') }} /><br />
  144. {t('security_setting.example')}1: <code>uid={'{{ username }}'},dc=domain,dc=com</code><br />
  145. {t('security_setting.example')}2: <code>{'{{ username }}'}@domain.com</code>
  146. </small>
  147. </p>
  148. )
  149. : (
  150. <p className="help-block passport-ldap-managerbind">
  151. <small>
  152. {t('security_setting.ldap.bind_DN_manager_detail')}<br />
  153. {t('security_setting.example')}1: <code>uid=admin,dc=domain,dc=com</code><br />
  154. {t('security_setting.example')}2: <code>admin@domain.com</code>
  155. </small>
  156. </p>
  157. )}
  158. </div>
  159. </div>
  160. <div className="row mb-5">
  161. <label htmlFor="bindDNPassword" className="col-xs-3 text-right">{t('security_setting.ldap.bind_DN_password')}</label>
  162. <div className="col-xs-6">
  163. {(adminLdapSecurityContainer.state.isUserBind) ? (
  164. <p className="help-block passport-ldap-userbind">
  165. <small>
  166. {t('security_setting.ldap.bind_DN_password_user_detail')}
  167. </small>
  168. </p>
  169. )
  170. : (
  171. <>
  172. <p className="help-block passport-ldap-managerbind">
  173. <small>
  174. {t('security_setting.ldap.bind_DN_password_manager_detail')}
  175. </small>
  176. </p>
  177. <input
  178. className="form-control passport-ldap-managerbind"
  179. type="password"
  180. name="bindDNPassword"
  181. defaultValue={adminLdapSecurityContainer.state.ldapBindDNPassword || ''}
  182. onChange={e => adminLdapSecurityContainer.changeBindDNPassword(e.target.value)}
  183. />
  184. </>
  185. )}
  186. </div>
  187. </div>
  188. <div className="row mb-5">
  189. <strong className="col-xs-3 text-right">{t('security_setting.ldap.search_filter')}</strong>
  190. <div className="col-xs-6">
  191. <input
  192. className="form-control"
  193. type="text"
  194. name="searchFilter"
  195. defaultValue={adminLdapSecurityContainer.state.ldapSearchFilter || ''}
  196. onChange={e => adminLdapSecurityContainer.changeSearchFilter(e.target.value)}
  197. />
  198. <p className="help-block">
  199. <small>
  200. {t('security_setting.ldap.search_filter_detail1')}<br />
  201. {/* eslint-disable-next-line react/no-danger */}
  202. <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.search_filter_detail2') }} /><br />
  203. {/* eslint-disable-next-line react/no-danger */}
  204. <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.search_filter_detail3') }} />
  205. </small>
  206. </p>
  207. <p className="help-block">
  208. <small>
  209. {t('security_setting.example')}1 - {t('security_setting.ldap.search_filter_example1')}:
  210. <code>(|(uid={'{{ username }}'})(mail={'{{ username }}'}))</code><br />
  211. {t('security_setting.example')}2 - {t('security_setting.ldap.search_filter_example2')}:
  212. <code>(sAMAccountName={'{{ username }}'})</code>
  213. </small>
  214. </p>
  215. </div>
  216. </div>
  217. <h3 className="alert-anchor border-bottom">
  218. Attribute Mapping ({t('security_setting.optional')})
  219. </h3>
  220. <div className="row mb-5">
  221. <strong htmlFor="attrMapUsername" className="col-xs-3 text-right">{t('username')}</strong>
  222. <div className="col-xs-6">
  223. <input
  224. className="form-control"
  225. type="text"
  226. placeholder="Default: uid"
  227. name="attrMapUsername"
  228. defaultValue={adminLdapSecurityContainer.state.ldapAttrMapUsername || ''}
  229. onChange={e => adminLdapSecurityContainer.changeAttrMapUsername(e.target.value)}
  230. />
  231. <p className="help-block">
  232. {/* eslint-disable-next-line react/no-danger */}
  233. <small dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.username_detail') }} />
  234. </p>
  235. </div>
  236. </div>
  237. <div className="row mb-5">
  238. <div className="col-xs-offset-3 col-xs-6 text-left">
  239. <div className="checkbox checkbox-success">
  240. <input
  241. id="isSameUsernameTreatedAsIdenticalUser"
  242. type="checkbox"
  243. checked={adminLdapSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser}
  244. onChange={() => { adminLdapSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
  245. />
  246. <label
  247. htmlFor="isSameUsernameTreatedAsIdenticalUser"
  248. // eslint-disable-next-line react/no-danger
  249. dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical') }}
  250. />
  251. </div>
  252. <p className="help-block">
  253. {/* eslint-disable-next-line react/no-danger */}
  254. <small dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical_warn') }} />
  255. </p>
  256. </div>
  257. </div>
  258. <div className="row mb-5">
  259. <strong htmlFor="attrMapMail" className="col-xs-3 text-right">{t('Email')}</strong>
  260. <div className="col-xs-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="help-block">
  270. <small>
  271. {t('security_setting.ldap.mail_detail')}
  272. </small>
  273. </p>
  274. </div>
  275. </div>
  276. <div className="row mb-5">
  277. <strong htmlFor="attrMapName" className="col-xs-3 text-right">{t('Name')}</strong>
  278. <div className="col-xs-6">
  279. <input
  280. className="form-control"
  281. type="text"
  282. name="attrMapName"
  283. defaultValue={adminLdapSecurityContainer.state.ldapAttrMapName || ''}
  284. onChange={e => adminLdapSecurityContainer.changeAttrMapName(e.target.value)}
  285. />
  286. <p className="help-block">
  287. <small>
  288. {t('security_setting.ldap.name_detail')}
  289. </small>
  290. </p>
  291. </div>
  292. </div>
  293. <h3 className="alert-anchor border-bottom">
  294. {t('security_setting.ldap.group_search_filter')} ({t('security_setting.optional')})
  295. </h3>
  296. <div className="row mb-5">
  297. <strong htmlFor="groupSearchBase" className="col-xs-3 text-right">{t('security_setting.ldap.group_search_base_DN')}</strong>
  298. <div className="col-xs-6">
  299. <input
  300. className="form-control"
  301. type="text"
  302. name="groupSearchBase"
  303. defaultValue={adminLdapSecurityContainer.state.ldapGroupSearchBase || ''}
  304. onChange={e => adminLdapSecurityContainer.changeGroupSearchBase(e.target.value)}
  305. />
  306. <p className="help-block">
  307. <small>
  308. {/* eslint-disable-next-line react/no-danger */}
  309. <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_base_DN_detail') }} /><br />
  310. {t('security_setting.example')}: <code>ou=groups,dc=domain,dc=com</code>
  311. </small>
  312. </p>
  313. </div>
  314. </div>
  315. <div className="row mb-5">
  316. <strong htmlFor="groupSearchFilter" className="col-xs-3 text-right">{t('security_setting.ldap.group_search_filter')}</strong>
  317. <div className="col-xs-6">
  318. <input
  319. className="form-control"
  320. type="text"
  321. name="groupSearchFilter"
  322. defaultValue={adminLdapSecurityContainer.state.ldapGroupSearchFilter || ''}
  323. onChange={e => adminLdapSecurityContainer.changeGroupSearchFilter(e.target.value)}
  324. />
  325. <p className="help-block">
  326. <small>
  327. {/* eslint-disable react/no-danger */}
  328. <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_filter_detail1') }} /><br />
  329. <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_filter_detail2') }} /><br />
  330. <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_filter_detail3') }} />
  331. {/* eslint-enable react/no-danger */}
  332. </small>
  333. </p>
  334. <p className="help-block">
  335. <small>
  336. {t('security_setting.example')}:
  337. {/* eslint-disable-next-line react/no-danger */}
  338. <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_filter_detail4') }} />
  339. </small>
  340. </p>
  341. </div>
  342. </div>
  343. <div className="row mb-5">
  344. <label htmlFor="groupDnProperty" className="col-xs-3 text-right">{t('security_setting.ldap.group_search_user_DN_property')}</label>
  345. <div className="col-xs-6">
  346. <input
  347. className="form-control"
  348. type="text"
  349. placeholder="Default: uid"
  350. name="groupDnProperty"
  351. defaultValue={adminLdapSecurityContainer.state.ldapGroupDnProperty || ''}
  352. onChange={e => adminLdapSecurityContainer.changeGroupDnProperty(e.target.value)}
  353. />
  354. <p className="help-block">
  355. {/* eslint-disable-next-line react/no-danger */}
  356. <small dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_user_DN_property_detail') }} />
  357. </p>
  358. </div>
  359. </div>
  360. <div className="row my-3">
  361. <div className="col-xs-offset-3 col-xs-5">
  362. <button
  363. type="button"
  364. className="btn btn-primary"
  365. disabled={adminLdapSecurityContainer.state.retrieveError != null}
  366. onClick={this.onClickSubmit}
  367. >
  368. {t('Update')}
  369. </button>
  370. <button type="button" className="btn btn-default ml-2" onClick={this.openLdapAuthTestModal}>{t('security_setting.ldap.test_config')}</button>
  371. </div>
  372. </div>
  373. </React.Fragment>
  374. )}
  375. <LdapAuthTestModal isOpen={this.state.isLdapAuthTestModalShown} onClose={this.closeLdapAuthTestModal} />
  376. </React.Fragment>
  377. );
  378. }
  379. }
  380. LdapSecuritySetting.propTypes = {
  381. t: PropTypes.func.isRequired, // i18next
  382. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  383. adminGeneralSecurityContainer: PropTypes.instanceOf(AdminGeneralSecurityContainer).isRequired,
  384. adminLdapSecurityContainer: PropTypes.instanceOf(AdminLdapSecurityContainer).isRequired,
  385. };
  386. const LdapSecuritySettingWrapper = (props) => {
  387. return createSubscribedElement(LdapSecuritySetting, props, [AppContainer, AdminGeneralSecurityContainer, AdminLdapSecurityContainer]);
  388. };
  389. export default withTranslation()(LdapSecuritySettingWrapper);