OidcSecuritySetting.jsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /* eslint-disable react/no-danger */
  2. import React from 'react';
  3. import PropTypes from 'prop-types';
  4. import { withTranslation } from 'react-i18next';
  5. import loggerFactory from '@alias/logger';
  6. import { createSubscribedElement } from '../../UnstatedUtils';
  7. import { toastSuccess, toastError } from '../../../util/apiNotification';
  8. import AppContainer from '../../../services/AppContainer';
  9. import AdminGeneralSecurityContainer from '../../../services/AdminGeneralSecurityContainer';
  10. import AdminOidcSecurityContainer from '../../../services/AdminOidcSecurityContainer';
  11. const logger = loggerFactory('growi:security:AdminGoogleSecurityContainer');
  12. class OidcSecurityManagement extends React.Component {
  13. constructor(props) {
  14. super(props);
  15. this.state = {
  16. retrieveError: null,
  17. };
  18. this.onClickSubmit = this.onClickSubmit.bind(this);
  19. }
  20. async componentDidMount() {
  21. const { adminOidcSecurityContainer } = this.props;
  22. try {
  23. await adminOidcSecurityContainer.retrieveSecurityData();
  24. }
  25. catch (err) {
  26. toastError(err);
  27. this.setState({ retrieveError: err });
  28. logger.error(err);
  29. }
  30. }
  31. async onClickSubmit() {
  32. const { t, adminOidcSecurityContainer } = this.props;
  33. try {
  34. await adminOidcSecurityContainer.updateOidcSetting();
  35. toastSuccess(t('security_setting.OAuth.OIDC.updated_oidc'));
  36. }
  37. catch (err) {
  38. toastError(err);
  39. logger.error(err);
  40. }
  41. }
  42. render() {
  43. const { t, adminGeneralSecurityContainer, adminOidcSecurityContainer } = this.props;
  44. return (
  45. <React.Fragment>
  46. <h2 className="alert-anchor border-bottom">
  47. {t('security_setting.OAuth.OIDC.name')} {t('security_setting.configuration')}
  48. </h2>
  49. <div className="row mb-5">
  50. <strong className="col-xs-3 text-right">{t('security_setting.OAuth.OIDC.name')}</strong>
  51. <div className="col-xs-6 text-left">
  52. <div className="checkbox checkbox-success">
  53. <input
  54. id="isOidcEnabled"
  55. type="checkbox"
  56. checked={adminGeneralSecurityContainer.state.isOidcEnabled}
  57. onChange={() => { adminGeneralSecurityContainer.switchIsOidcEnabled() }}
  58. />
  59. <label htmlFor="isOidcEnabled">
  60. {t('security_setting.OAuth.enable_oidc')}
  61. </label>
  62. </div>
  63. </div>
  64. </div>
  65. <div className="row mb-5">
  66. <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
  67. <div className="col-xs-6">
  68. <input
  69. className="form-control"
  70. type="text"
  71. value={adminOidcSecurityContainer.state.callbackUrl}
  72. readOnly
  73. />
  74. <p className="help-block small">{t('security_setting.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
  75. {!adminGeneralSecurityContainer.state.appSiteUrl && (
  76. <div className="alert alert-danger">
  77. <i
  78. className="icon-exclamation"
  79. // eslint-disable-next-line max-len
  80. dangerouslySetInnerHTML={{ __html: t('security_setting.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('App settings')}<i class="icon-login"></i></a>` }) }}
  81. />
  82. </div>
  83. )}
  84. </div>
  85. </div>
  86. {adminGeneralSecurityContainer.state.isOidcEnabled && (
  87. <React.Fragment>
  88. <div className="row mb-5">
  89. <label htmlFor="oidcProviderName" className="col-xs-3 text-right">{t('security_setting.providerName')}</label>
  90. <div className="col-xs-6">
  91. <input
  92. className="form-control"
  93. type="text"
  94. name="oidcProviderName"
  95. value={adminOidcSecurityContainer.state.oidcProviderName}
  96. onChange={e => adminOidcSecurityContainer.changeOidcProviderName(e.target.value)}
  97. />
  98. </div>
  99. </div>
  100. <div className="row mb-5">
  101. <label htmlFor="oidcIssuerHost" className="col-xs-3 text-right">{t('security_setting.issuerHost')}</label>
  102. <div className="col-xs-6">
  103. <input
  104. className="form-control"
  105. type="text"
  106. name="oidcIssuerHost"
  107. value={adminOidcSecurityContainer.state.oidcIssuerHost}
  108. onChange={e => adminOidcSecurityContainer.changeOidcIssuerHost(e.target.value)}
  109. />
  110. <p className="help-block">
  111. <small dangerouslySetInnerHTML={{ __html: t('security_setting.Use env var if empty', { env: 'OAUTH_OIDC_ISSUER_HOST' }) }} />
  112. </p>
  113. </div>
  114. </div>
  115. <div className="row mb-5">
  116. <label htmlFor="oidcClientId" className="col-xs-3 text-right">{t('security_setting.clientID')}</label>
  117. <div className="col-xs-6">
  118. <input
  119. className="form-control"
  120. type="text"
  121. name="oidcClientId"
  122. value={adminOidcSecurityContainer.state.oidcClientId}
  123. onChange={e => adminOidcSecurityContainer.changeOidcClientId(e.target.value)}
  124. />
  125. <p className="help-block">
  126. <small dangerouslySetInnerHTML={{ __html: t('security_setting.Use env var if empty', { env: 'OAUTH_OIDC_CLIENT_ID' }) }} />
  127. </p>
  128. </div>
  129. </div>
  130. <div className="row mb-5">
  131. <label htmlFor="oidcClientSecret" className="col-xs-3 text-right">{t('security_setting.client_secret')}</label>
  132. <div className="col-xs-6">
  133. <input
  134. className="form-control"
  135. type="text"
  136. name="oidcClientSecret"
  137. value={adminOidcSecurityContainer.state.oidcClientSecret}
  138. onChange={e => adminOidcSecurityContainer.changeOidcClientSecret(e.target.value)}
  139. />
  140. <p className="help-block">
  141. <small dangerouslySetInnerHTML={{ __html: t('security_setting.Use env var if empty', { env: 'OAUTH_OIDC_CLIENT_SECRET' }) }} />
  142. </p>
  143. </div>
  144. </div>
  145. <h3 className="alert-anchor border-bottom">
  146. Attribute Mapping ({t('security_setting.optional')})
  147. </h3>
  148. <div className="row mb-5">
  149. <label htmlFor="oidcAttrMapId" className="col-xs-3 text-right">Identifier</label>
  150. <div className="col-xs-6">
  151. <input
  152. className="form-control"
  153. type="text"
  154. name="oidcAttrMapId"
  155. value={adminOidcSecurityContainer.state.oidcAttrMapId}
  156. onChange={e => adminOidcSecurityContainer.changeOidcAttrMapId(e.target.value)}
  157. />
  158. <p className="help-block">
  159. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.id_detail') }} />
  160. </p>
  161. </div>
  162. </div>
  163. <div className="row mb-5">
  164. <label htmlFor="oidcAttrMapUserName" className="col-xs-3 text-right">{t('username')}</label>
  165. <div className="col-xs-6">
  166. <input
  167. className="form-control"
  168. type="text"
  169. name="oidcAttrMapUserName"
  170. value={adminOidcSecurityContainer.state.oidcAttrMapUserName}
  171. onChange={e => adminOidcSecurityContainer.changeOidcAttrMapUserName(e.target.value)}
  172. />
  173. <p className="help-block">
  174. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.username_detail') }} />
  175. </p>
  176. </div>
  177. </div>
  178. <div className="row mb-5">
  179. <label htmlFor="oidcAttrMapName" className="col-xs-3 text-right">{t('Name')}</label>
  180. <div className="col-xs-6">
  181. <input
  182. className="form-control"
  183. type="text"
  184. name="oidcAttrMapName"
  185. value={adminOidcSecurityContainer.state.oidcAttrMapName}
  186. onChange={e => adminOidcSecurityContainer.changeOidcAttrMapName(e.target.value)}
  187. />
  188. <p className="help-block">
  189. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.name_detail') }} />
  190. </p>
  191. </div>
  192. </div>
  193. <div className="row mb-5">
  194. <label htmlFor="oidcAttrMapEmail" className="col-xs-3 text-right">{t('Email')}</label>
  195. <div className="col-xs-6">
  196. <input
  197. className="form-control"
  198. type="text"
  199. name="oidcAttrMapEmail"
  200. value={adminOidcSecurityContainer.state.oidcAttrMapEmail}
  201. onChange={e => adminOidcSecurityContainer.changeOidcAttrMapEmail(e.target.value)}
  202. />
  203. <p className="help-block">
  204. <small dangerouslySetInnerHTML={{ __html: t('security_setting.OAuth.OIDC.mapping_detail', { target: t('Email') }) }} />
  205. </p>
  206. </div>
  207. </div>
  208. <div className="row mb-5">
  209. <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
  210. <div className="col-xs-6">
  211. <input
  212. className="form-control"
  213. type="text"
  214. value={adminOidcSecurityContainer.state.callbackUrl}
  215. readOnly
  216. />
  217. <p className="help-block small">{t('security_setting.desc_of_callback_URL', { AuthName: 'OAuth' })}</p>
  218. {!adminGeneralSecurityContainer.state.appSiteUrl && (
  219. <div className="alert alert-danger">
  220. <i
  221. className="icon-exclamation"
  222. // eslint-disable-next-line max-len
  223. dangerouslySetInnerHTML={{ __html: t('security_setting.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('App settings')}<i class="icon-login"></i></a>` }) }}
  224. />
  225. </div>
  226. )}
  227. </div>
  228. </div>
  229. <div className="row mb-3">
  230. <div className="col-xs-offset-3 col-xs-6 text-left">
  231. <div className="checkbox checkbox-success">
  232. <input
  233. id="bindByUserName-oidc"
  234. type="checkbox"
  235. checked={adminOidcSecurityContainer.state.isSameUsernameTreatedAsIdenticalUser}
  236. onChange={() => { adminOidcSecurityContainer.switchIsSameUsernameTreatedAsIdenticalUser() }}
  237. />
  238. <label
  239. htmlFor="bindByUserName-oidc"
  240. dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical') }}
  241. />
  242. </div>
  243. <p className="help-block">
  244. <small dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical_warn') }} />
  245. </p>
  246. </div>
  247. </div>
  248. <div className="row mb-5">
  249. <div className="col-xs-offset-3 col-xs-6 text-left">
  250. <div className="checkbox checkbox-success">
  251. <input
  252. id="bindByEmail-oidc"
  253. type="checkbox"
  254. checked={adminOidcSecurityContainer.state.isSameEmailTreatedAsIdenticalUser}
  255. onChange={() => { adminOidcSecurityContainer.switchIsSameEmailTreatedAsIdenticalUser() }}
  256. />
  257. <label
  258. htmlFor="bindByEmail-oidc"
  259. dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical') }}
  260. />
  261. </div>
  262. <p className="help-block">
  263. <small dangerouslySetInnerHTML={{ __html: t('security_setting.Treat email matching as identical_warn') }} />
  264. </p>
  265. </div>
  266. </div>
  267. </React.Fragment>
  268. )}
  269. <div className="row my-3">
  270. <div className="col-xs-offset-3 col-xs-5">
  271. <button type="button" className="btn btn-primary" disabled={this.state.retrieveError != null} onClick={this.onClickSubmit}>{t('Update')}</button>
  272. </div>
  273. </div>
  274. <hr />
  275. <div style={{ minHeight: '300px' }}>
  276. <h4>
  277. <i className="icon-question" aria-hidden="true"></i>
  278. <a href="#collapseHelpForOidcOauth" data-toggle="collapse">{t('security_setting.OAuth.how_to.oidc')}</a>
  279. </h4>
  280. <ol id="collapseHelpForOidcOauth" className="collapse">
  281. <li>{t('security_setting.OAuth.OIDC.register_1')}</li>
  282. <li>{t('security_setting.OAuth.OIDC.register_2')}</li>
  283. <li>{t('security_setting.OAuth.OIDC.register_3')}</li>
  284. </ol>
  285. </div>
  286. </React.Fragment>
  287. );
  288. }
  289. }
  290. OidcSecurityManagement.propTypes = {
  291. t: PropTypes.func.isRequired, // i18next
  292. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  293. adminGeneralSecurityContainer: PropTypes.instanceOf(AdminGeneralSecurityContainer).isRequired,
  294. adminOidcSecurityContainer: PropTypes.instanceOf(AdminOidcSecurityContainer).isRequired,
  295. };
  296. const OidcSecurityManagementWrapper = (props) => {
  297. return createSubscribedElement(OidcSecurityManagement, props, [AppContainer, AdminGeneralSecurityContainer, AdminOidcSecurityContainer]);
  298. };
  299. export default withTranslation()(OidcSecurityManagementWrapper);