LdapSecuritySetting.jsx 18 KB

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