OidcSecuritySettingContents.jsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. /* eslint-disable react/no-danger */
  2. import React from 'react';
  3. import PropTypes from 'prop-types';
  4. import { useTranslation } from 'react-i18next';
  5. import AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
  6. import AdminOidcSecurityContainer from '~/client/services/AdminOidcSecurityContainer';
  7. import { toastSuccess, toastError } from '~/client/util/apiNotification';
  8. import { withUnstatedContainers } from '../../UnstatedUtils';
  9. class OidcSecurityManagementContents extends React.Component {
  10. constructor(props) {
  11. super(props);
  12. this.onClickSubmit = this.onClickSubmit.bind(this);
  13. }
  14. async onClickSubmit() {
  15. const { t, adminOidcSecurityContainer, adminGeneralSecurityContainer } = this.props;
  16. try {
  17. await adminOidcSecurityContainer.updateOidcSetting();
  18. await adminGeneralSecurityContainer.retrieveSetupStratedies();
  19. toastSuccess(t('security_setting.OAuth.OIDC.updated_oidc'));
  20. }
  21. catch (err) {
  22. toastError(err);
  23. }
  24. }
  25. render() {
  26. const { t, adminGeneralSecurityContainer, adminOidcSecurityContainer } = this.props;
  27. const { isOidcEnabled } = adminGeneralSecurityContainer.state;
  28. return (
  29. <React.Fragment>
  30. <h2 className="alert-anchor border-bottom">
  31. {t('security_setting.OAuth.OIDC.name')}
  32. </h2>
  33. <div className="row mb-5 form-group">
  34. <div className="offset-3 col-6">
  35. <div className="custom-control custom-switch custom-checkbox-success">
  36. <input
  37. id="isOidcEnabled"
  38. className="custom-control-input"
  39. type="checkbox"
  40. checked={adminGeneralSecurityContainer.state.isOidcEnabled}
  41. onChange={() => { adminGeneralSecurityContainer.switchIsOidcEnabled() }}
  42. />
  43. <label className="custom-control-label" htmlFor="isOidcEnabled">
  44. {t('security_setting.OAuth.enable_oidc')}
  45. </label>
  46. </div>
  47. {(!adminGeneralSecurityContainer.state.setupStrategies.includes('oidc') && isOidcEnabled)
  48. && <div className="badge badge-warning">{t('security_setting.setup_is_not_yet_complete')}</div>}
  49. </div>
  50. </div>
  51. <div className="row mb-5 form-group">
  52. <label className="text-left text-md-right col-md-3 col-form-label">{t('security_setting.callback_URL')}</label>
  53. <div className="col-md-6">
  54. <input
  55. className="form-control"
  56. type="text"
  57. value={adminOidcSecurityContainer.state.callbackUrl}
  58. readOnly
  59. />
  60. <p className="form-text text-muted small">{t('security_setting.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
  61. {!adminGeneralSecurityContainer.state.appSiteUrl && (
  62. <div className="alert alert-danger">
  63. <i
  64. className="icon-exclamation"
  65. // eslint-disable-next-line max-len
  66. dangerouslySetInnerHTML={{ __html: t('security_setting.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('App Settings')}<i class="icon-login"></i></a>` }) }}
  67. />
  68. </div>
  69. )}
  70. </div>
  71. </div>
  72. {isOidcEnabled && (
  73. <React.Fragment>
  74. <h3 className="border-bottom">{t('security_setting.configuration')}</h3>
  75. <div className="row mb-5 form-group">
  76. <label htmlFor="oidcProviderName" className="text-left text-md-right col-md-3 col-form-label">{t('security_setting.providerName')}</label>
  77. <div className="col-md-6">
  78. <input
  79. className="form-control"
  80. type="text"
  81. name="oidcProviderName"
  82. defaultValue={adminOidcSecurityContainer.state.oidcProviderName || ''}
  83. onChange={e => adminOidcSecurityContainer.changeOidcProviderName(e.target.value)}
  84. />
  85. </div>
  86. </div>
  87. <div className="row mb-5 form-group">
  88. <label htmlFor="oidcIssuerHost" className="text-left text-md-right col-md-3 col-form-label">{t('security_setting.issuerHost')}</label>
  89. <div className="col-md-6">
  90. <input
  91. className="form-control"
  92. type="text"
  93. name="oidcIssuerHost"
  94. defaultValue={adminOidcSecurityContainer.state.oidcIssuerHost || ''}
  95. onChange={e => adminOidcSecurityContainer.changeOidcIssuerHost(e.target.value)}
  96. />
  97. <p className="form-text text-muted">
  98. <small dangerouslySetInnerHTML={{ __html: t('security_setting.Use env var if empty', { env: 'OAUTH_OIDC_ISSUER_HOST' }) }} />
  99. </p>
  100. </div>
  101. </div>
  102. <div className="row mb-5 form-group">
  103. <label htmlFor="oidcClientId" className="text-left text-md-right col-md-3 col-form-label">{t('security_setting.clientID')}</label>
  104. <div className="col-md-6">
  105. <input
  106. className="form-control"
  107. type="text"
  108. name="oidcClientId"
  109. defaultValue={adminOidcSecurityContainer.state.oidcClientId || ''}
  110. onChange={e => adminOidcSecurityContainer.changeOidcClientId(e.target.value)}
  111. />
  112. <p className="form-text text-muted">
  113. <small dangerouslySetInnerHTML={{ __html: t('security_setting.Use env var if empty', { env: 'OAUTH_OIDC_CLIENT_ID' }) }} />
  114. </p>
  115. </div>
  116. </div>
  117. <div className="row mb-5 form-group">
  118. <label htmlFor="oidcClientSecret" className="text-left text-md-right col-md-3 col-form-label">{t('security_setting.client_secret')}</label>
  119. <div className="col-md-6">
  120. <input
  121. className="form-control"
  122. type="text"
  123. name="oidcClientSecret"
  124. defaultValue={adminOidcSecurityContainer.state.oidcClientSecret || ''}
  125. onChange={e => adminOidcSecurityContainer.changeOidcClientSecret(e.target.value)}
  126. />
  127. <p className="form-text text-muted">
  128. <small dangerouslySetInnerHTML={{ __html: t('security_setting.Use env var if empty', { env: 'OAUTH_OIDC_CLIENT_SECRET' }) }} />
  129. </p>
  130. </div>
  131. </div>
  132. <div className="row mb-5 form-group">
  133. <label htmlFor="oidcAuthorizationEndpoint" className="text-left text-md-right col-md-3 col-form-label">
  134. {t('security_setting.authorization_endpoint')}
  135. </label>
  136. <div className="col-md-6">
  137. <input
  138. className="form-control"
  139. type="text"
  140. name="oidcAuthorizationEndpoint"
  141. defaultValue={adminOidcSecurityContainer.state.oidcAuthorizationEndpoint || ''}
  142. onChange={e => adminOidcSecurityContainer.changeOidcAuthorizationEndpoint(e.target.value)}
  143. />
  144. <p className="form-text text-muted">
  145. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.Use discovered URL if empty') }} />
  146. </p>
  147. </div>
  148. </div>
  149. <div className="row mb-5 form-group">
  150. <label htmlFor="oidcTokenEndpoint" className="text-left text-md-right col-md-3 col-form-label">{t('security_setting.token_endpoint')}</label>
  151. <div className="col-md-6">
  152. <input
  153. className="form-control"
  154. type="text"
  155. name="oidcTokenEndpoint"
  156. defaultValue={adminOidcSecurityContainer.state.oidcTokenEndpoint || ''}
  157. onChange={e => adminOidcSecurityContainer.changeOidcTokenEndpoint(e.target.value)}
  158. />
  159. <p className="form-text text-muted">
  160. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.Use discovered URL if empty') }} />
  161. </p>
  162. </div>
  163. </div>
  164. <div className="row mb-5 form-group">
  165. <label htmlFor="oidcRevocationEndpoint" className="text-left text-md-right col-md-3 col-form-label">
  166. {t('security_setting.revocation_endpoint')}
  167. </label>
  168. <div className="col-md-6">
  169. <input
  170. className="form-control"
  171. type="text"
  172. name="oidcRevocationEndpoint"
  173. defaultValue={adminOidcSecurityContainer.state.oidcRevocationEndpoint || ''}
  174. onChange={e => adminOidcSecurityContainer.changeOidcRevocationEndpoint(e.target.value)}
  175. />
  176. <p className="form-text text-muted">
  177. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.Use discovered URL if empty') }} />
  178. </p>
  179. </div>
  180. </div>
  181. <div className="row mb-5 form-group">
  182. <label htmlFor="oidcIntrospectionEndpoint" className="text-left text-md-right col-md-3 col-form-label">
  183. {t('security_setting.introspection_endpoint')}
  184. </label>
  185. <div className="col-md-6">
  186. <input
  187. className="form-control"
  188. type="text"
  189. name="oidcIntrospectionEndpoint"
  190. defaultValue={adminOidcSecurityContainer.state.oidcIntrospectionEndpoint || ''}
  191. onChange={e => adminOidcSecurityContainer.changeOidcIntrospectionEndpoint(e.target.value)}
  192. />
  193. <p className="form-text text-muted">
  194. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.Use discovered URL if empty') }} />
  195. </p>
  196. </div>
  197. </div>
  198. <div className="row mb-5 form-group">
  199. <label htmlFor="oidcUserInfoEndpoint" className="text-left text-md-right col-md-3 col-form-label">
  200. {t('security_setting.userinfo_endpoint')}
  201. </label>
  202. <div className="col-md-6">
  203. <input
  204. className="form-control"
  205. type="text"
  206. name="oidcUserInfoEndpoint"
  207. defaultValue={adminOidcSecurityContainer.state.oidcUserInfoEndpoint || ''}
  208. onChange={e => adminOidcSecurityContainer.changeOidcUserInfoEndpoint(e.target.value)}
  209. />
  210. <p className="form-text text-muted">
  211. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.Use discovered URL if empty') }} />
  212. </p>
  213. </div>
  214. </div>
  215. <div className="row mb-5 form-group">
  216. <label htmlFor="oidcEndSessionEndpoint" className="text-left text-md-right col-md-3 col-form-label">
  217. {t('security_setting.end_session_endpoint')}
  218. </label>
  219. <div className="col-md-6">
  220. <input
  221. className="form-control"
  222. type="text"
  223. name="oidcEndSessionEndpoint"
  224. defaultValue={adminOidcSecurityContainer.state.oidcEndSessionEndpoint || ''}
  225. onChange={e => adminOidcSecurityContainer.changeOidcEndSessionEndpoint(e.target.value)}
  226. />
  227. <p className="form-text text-muted">
  228. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.Use discovered URL if empty') }} />
  229. </p>
  230. </div>
  231. </div>
  232. <div className="row mb-5 form-group">
  233. <label htmlFor="oidcRegistrationEndpoint" className="text-left text-md-right col-md-3 col-form-label">
  234. {t('security_setting.registration_endpoint')}
  235. </label>
  236. <div className="col-md-6">
  237. <input
  238. className="form-control"
  239. type="text"
  240. name="oidcRegistrationEndpoint"
  241. defaultValue={adminOidcSecurityContainer.state.oidcRegistrationEndpoint || ''}
  242. onChange={e => adminOidcSecurityContainer.changeOidcRegistrationEndpoint(e.target.value)}
  243. />
  244. <p className="form-text text-muted">
  245. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.Use discovered URL if empty') }} />
  246. </p>
  247. </div>
  248. </div>
  249. <div className="row mb-5 form-group">
  250. <label htmlFor="oidcJWKSUri" className="text-left text-md-right col-md-3 col-form-label">{t('security_setting.jwks_uri')}</label>
  251. <div className="col-md-6">
  252. <input
  253. className="form-control"
  254. type="text"
  255. name="oidcJWKSUri"
  256. defaultValue={adminOidcSecurityContainer.state.oidcJWKSUri || ''}
  257. onChange={e => adminOidcSecurityContainer.changeOidcJWKSUri(e.target.value)}
  258. />
  259. <p className="form-text text-muted">
  260. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.Use discovered URL if empty') }} />
  261. </p>
  262. </div>
  263. </div>
  264. <h3 className="alert-anchor border-bottom">
  265. Attribute Mapping ({t('security_setting.optional')})
  266. </h3>
  267. <div className="row mb-5 form-group">
  268. <label htmlFor="oidcAttrMapId" className="text-left text-md-right col-md-3 col-form-label">Identifier</label>
  269. <div className="col-md-6">
  270. <input
  271. className="form-control"
  272. type="text"
  273. name="oidcAttrMapId"
  274. defaultValue={adminOidcSecurityContainer.state.oidcAttrMapId || ''}
  275. onChange={e => adminOidcSecurityContainer.changeOidcAttrMapId(e.target.value)}
  276. />
  277. <p className="form-text text-muted">
  278. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.id_detail') }} />
  279. </p>
  280. </div>
  281. </div>
  282. <div className="row mb-5 form-group">
  283. <label htmlFor="oidcAttrMapUserName" className="text-left text-md-right col-md-3 col-form-label">{t('username')}</label>
  284. <div className="col-md-6">
  285. <input
  286. className="form-control"
  287. type="text"
  288. name="oidcAttrMapUserName"
  289. defaultValue={adminOidcSecurityContainer.state.oidcAttrMapUserName || ''}
  290. onChange={e => adminOidcSecurityContainer.changeOidcAttrMapUserName(e.target.value)}
  291. />
  292. <p className="form-text text-muted">
  293. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.username_detail') }} />
  294. </p>
  295. </div>
  296. </div>
  297. <div className="row mb-5 form-group">
  298. <label htmlFor="oidcAttrMapName" className="text-left text-md-right col-md-3 col-form-label">{t('Name')}</label>
  299. <div className="col-md-6">
  300. <input
  301. className="form-control"
  302. type="text"
  303. name="oidcAttrMapName"
  304. defaultValue={adminOidcSecurityContainer.state.oidcAttrMapName || ''}
  305. onChange={e => adminOidcSecurityContainer.changeOidcAttrMapName(e.target.value)}
  306. />
  307. <p className="form-text text-muted">
  308. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.name_detail') }} />
  309. </p>
  310. </div>
  311. </div>
  312. <div className="row mb-5 form-group">
  313. <label htmlFor="oidcAttrMapEmail" className="text-left text-md-right col-md-3 col-form-label">{t('Email')}</label>
  314. <div className="col-md-6">
  315. <input
  316. className="form-control"
  317. type="text"
  318. name="oidcAttrMapEmail"
  319. defaultValue={adminOidcSecurityContainer.state.oidcAttrMapEmail || ''}
  320. onChange={e => adminOidcSecurityContainer.changeOidcAttrMapEmail(e.target.value)}
  321. />
  322. <p className="form-text text-muted">
  323. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.mapping_detail', { target: t('Email') }) }} />
  324. </p>
  325. </div>
  326. </div>
  327. <div className="row mb-5 form-group">
  328. <label className="text-left text-md-right col-md-3 col-form-label">{t('security_setting.callback_URL')}</label>
  329. <div className="col-md-6">
  330. <input
  331. className="form-control"
  332. type="text"
  333. defaultValue={adminOidcSecurityContainer.state.callbackUrl || ''}
  334. readOnly
  335. />
  336. <p className="form-text text-muted small">{t('security_setting.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
  337. {!adminGeneralSecurityContainer.state.appSiteUrl && (
  338. <div className="alert alert-danger">
  339. <i
  340. className="icon-exclamation"
  341. // eslint-disable-next-line max-len
  342. dangerouslySetInnerHTML={{ __html: t('security_setting.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('App Settings')}<i class="icon-login"></i></a>` }) }}
  343. />
  344. </div>
  345. )}
  346. </div>
  347. </div>
  348. <div className="row mb-5 form-group">
  349. <div className="offset-md-3 col-md-6">
  350. <div className="custom-control custom-checkbox custom-checkbox-success">
  351. <input
  352. id="bindByUserName-oidc"
  353. className="custom-control-input"
  354. type="checkbox"
  355. checked={adminOidcSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser}
  356. onChange={() => { adminOidcSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
  357. />
  358. <label
  359. className="custom-control-label"
  360. htmlFor="bindByUserName-oidc"
  361. dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical') }}
  362. />
  363. </div>
  364. <p className="form-text text-muted">
  365. <small dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical_warn') }} />
  366. </p>
  367. </div>
  368. </div>
  369. <div className="row mb-5 form-group">
  370. <div className="offset-md-3 col-md-6">
  371. <div className="custom-control custom-checkbox custom-checkbox-success">
  372. <input
  373. id="bindByEmail-oidc"
  374. className="custom-control-input"
  375. type="checkbox"
  376. checked={adminOidcSecurityContainer.state.isSameEmailTreatedAsIdenticalUser || false}
  377. onChange={() => { adminOidcSecurityContainer.switchIsSameEmailTreatedAsIdenticalUser() }}
  378. />
  379. <label
  380. className="custom-control-label"
  381. htmlFor="bindByEmail-oidc"
  382. dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
  383. />
  384. </div>
  385. <p className="form-text text-muted">
  386. <small dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical_warn') }} />
  387. </p>
  388. </div>
  389. </div>
  390. <div className="row my-3">
  391. <div className="offset-3 col-5">
  392. <button
  393. type="button"
  394. className="btn btn-primary"
  395. disabled={adminOidcSecurityContainer.state.retrieveError != null}
  396. onClick={this.onClickSubmit}
  397. >
  398. {t('Update')}
  399. </button>
  400. </div>
  401. </div>
  402. </React.Fragment>
  403. )}
  404. <hr />
  405. <div style={{ minHeight: '300px' }}>
  406. <h4>
  407. <i className="icon-question" aria-hidden="true" />
  408. <a href="#collapseHelpForOidcOauth" data-toggle="collapse"> {t('security_setting.OAuth.how_to.oidc')}</a>
  409. </h4>
  410. <ol id="collapseHelpForOidcOauth" className="collapse">
  411. <li>{t('security_setting.OAuth.OIDC.register_1')}</li>
  412. <li>{t('security_setting.OAuth.OIDC.register_2')}</li>
  413. <li>{t('security_setting.OAuth.OIDC.register_3')}</li>
  414. </ol>
  415. </div>
  416. </React.Fragment>
  417. );
  418. }
  419. }
  420. OidcSecurityManagementContents.propTypes = {
  421. t: PropTypes.func.isRequired, // i18next
  422. adminGeneralSecurityContainer: PropTypes.instanceOf(AdminGeneralSecurityContainer).isRequired,
  423. adminOidcSecurityContainer: PropTypes.instanceOf(AdminOidcSecurityContainer).isRequired,
  424. };
  425. const OidcSecurityManagementContentsWrapperFC = (props) => {
  426. const { t } = useTranslation();
  427. return <OidcSecurityManagementContents t={t} {...props} />;
  428. };
  429. const OidcSecurityManagementContentsWrapper = withUnstatedContainers(OidcSecurityManagementContentsWrapperFC, [
  430. AdminGeneralSecurityContainer,
  431. AdminOidcSecurityContainer,
  432. ]);
  433. export default OidcSecurityManagementContentsWrapper;