Просмотр исходного кода

Merge pull request #1460 from weseek/reactify-admin/create-apiV3-update-saml-setting

Reactify admin/create api v3 update saml setting
Yuki Takei 6 лет назад
Родитель
Сommit
91c432bbc8

+ 2 - 1
resource/locales/en-US/translation.json

@@ -538,7 +538,8 @@
       "mapping_detail": "Specification of mappings for {{target}} when creating new users",
       "cert_detail": "PEM-encoded X.509 signing certificate to validate the response from IdP",
       "Use env var if empty": "If the value in the database is empty, the value of the environment variable <code>{{env}}</code> is used.",
-      "note for the only env option": "The setting item that enables or disables the SAML authentication and the highlighted setting items use only the value of environment variables.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> ."
+      "note for the only env option": "The setting item that enables or disables the SAML authentication and the highlighted setting items use only the value of environment variables.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
+      "updated_saml": "Succeeded to update SAML setting"
     },
     "Basic": {
       "enable_basic":"enable Basic",

+ 2 - 1
resource/locales/ja/translation.json

@@ -533,7 +533,8 @@
       "mapping_detail": "新規ユーザーの{{target}}に関連付ける属性",
       "cert_detail": "IdP からのレスポンスの validation を行うためのPEMエンコードされた X.509 証明書",
       "Use env var if empty": "データベース側の値が空の場合、環境変数 <code>{{env}}</code> の値を利用します",
-      "note for the only env option": "現在SAML認証のON/OFFの設定値及びハイライトされている設定値は環境変数の値のみを使用するようになっています<br>この設定を変更する場合は環境変数 <code>{{env}}</code> の値をfalseに変更もしくは削除してください"
+      "note for the only env option": "現在SAML認証のON/OFFの設定値及びハイライトされている設定値は環境変数の値のみを使用するようになっています<br>この設定を変更する場合は環境変数 <code>{{env}}</code> の値をfalseに変更もしくは削除してください",
+      "updated_saml": "SAML設定 を更新しました"
     },
     "Basic": {
       "enable_basic":"Basic を有効にする",

+ 6 - 0
src/client/js/components/Admin/Security/OidcSecuritySetting.jsx

@@ -62,6 +62,12 @@ class OidcSecurityManagement extends React.Component {
           {t('security_setting.OAuth.OIDC.name')} {t('security_setting.configuration')}
         </h2>
 
+        {this.state.retrieveError != null && (
+          <div className="alert alert-danger">
+            <p>{t('Error occurred')} : {this.state.err}</p>
+          </div>
+        )}
+
         <div className="row mb-5">
           <strong className="col-xs-3 text-right">{t('security_setting.OAuth.OIDC.name')}</strong>
           <div className="col-xs-6 text-left">

+ 132 - 63
src/client/js/components/Admin/Security/SamlSecuritySetting.jsx

@@ -2,16 +2,73 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import loggerFactory from '@alias/logger';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
 
 import AppContainer from '../../../services/AppContainer';
 import AdminGeneralSecurityContainer from '../../../services/AdminGeneralSecurityContainer';
 import AdminSamlSecurityContainer from '../../../services/AdminSamlSecurityContainer';
 
+const logger = loggerFactory('growi:security:AdminSamlSecurityContainer');
 
 class SamlSecurityManagement extends React.Component {
 
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      retrieveError: null,
+      envEntryPoint: '',
+      envIssuer: '',
+      envCert: '',
+      envAttrMapId: '',
+      envAttrMapUserName: '',
+      envAttrMapMail: '',
+      envAttrMapFirstName: '',
+      envAttrMapLastName: '',
+    };
+
+    this.onClickSubmit = this.onClickSubmit.bind(this);
+  }
+
+  async componentDidMount() {
+    const { adminSamlSecurityContainer } = this.props;
+
+    try {
+      const samlAuth = await adminSamlSecurityContainer.retrieveSecurityData();
+      this.setState({
+        envEntryPoint: samlAuth.samlEnvVarEntryPoint || '',
+        envIssuer: samlAuth.samlEnvVarIssuer || '',
+        envCert: samlAuth.samlEnvVarCert || '',
+        envAttrMapId: samlAuth.samlEnvVarAttrMapId || '',
+        envAttrMapUserName: samlAuth.samlEnvVarAttrMapUserName || '',
+        envAttrMapMail: samlAuth.samlEnvVarAttrMapMail || '',
+        envAttrMapFirstName: samlAuth.samlEnvVarAttrMapFirstName || '',
+        envAttrMapLastName: samlAuth.samlEnvVarAttrMapLastName || '',
+      });
+    }
+    catch (err) {
+      toastError(err);
+      this.setState({ retrieveError: err });
+      logger.error(err);
+    }
+  }
+
+  async onClickSubmit() {
+    const { t, adminSamlSecurityContainer } = this.props;
+
+    try {
+      await adminSamlSecurityContainer.updateSamlSetting();
+      toastSuccess(t('security_setting.SAML.updated_saml'));
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
+
   render() {
     const { t, adminGeneralSecurityContainer, adminSamlSecurityContainer } = this.props;
     const { useOnlyEnvVars } = adminSamlSecurityContainer.state;
@@ -20,18 +77,24 @@ class SamlSecurityManagement extends React.Component {
       <React.Fragment>
 
         <h2 className="alert-anchor border-bottom">
-          { t('security_setting.SAML.name') } { t('security_setting.configuration') }
+          {t('security_setting.SAML.name')} {t('security_setting.configuration')}
         </h2>
 
+        {this.state.retrieveError != null && (
+          <div className="alert alert-danger">
+            <p>{t('Error occurred')} : {this.state.err}</p>
+          </div>
+        )}
+
         {useOnlyEnvVars && (
-        <p
-          className="alert alert-info"
-          dangerouslySetInnerHTML={{ __html: t('security_setting.SAML.note for the only env option', { env: 'SAML_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS' }) }}
-        />
+          <p
+            className="alert alert-info"
+            dangerouslySetInnerHTML={{ __html: t('security_setting.SAML.note for the only env option', { env: 'SAML_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS' }) }}
+          />
         )}
 
         <div className="row mb-5">
-          <strong className="col-xs-3 text-right">{ t('security_setting.SAML.name') }</strong>
+          <strong className="col-xs-3 text-right">{t('security_setting.SAML.name')}</strong>
           <div className="col-xs-6 text-left">
             <div className="checkbox checkbox-success">
               <input
@@ -41,30 +104,30 @@ class SamlSecurityManagement extends React.Component {
                 onChange={() => { adminGeneralSecurityContainer.switchIsSamlEnabled() }}
               />
               <label htmlFor="isSamlEnabled">
-                { t('security_setting.SAML.enable_saml') }
+                {t('security_setting.SAML.enable_saml')}
               </label>
             </div>
           </div>
         </div>
 
         <div className="row mb-5">
-          <label className="col-xs-3 text-right">{ t('security_setting.callback_URL') }</label>
+          <label className="col-xs-3 text-right">{t('security_setting.callback_URL')}</label>
           <div className="col-xs-6">
             <input
               className="form-control"
               type="text"
-              value={adminSamlSecurityContainer.state.callbackUrl}
+              defaultValue={adminSamlSecurityContainer.state.callbackUrl}
               readOnly
             />
-            <p className="help-block small">{ t('security_setting.desc_of_callback_URL', { AuthName: 'SAML Identity' }) }</p>
-            {!adminSamlSecurityContainer.state.appSiteUrl && (
-            <div className="alert alert-danger">
-              <i
-                className="icon-exclamation"
-                // eslint-disable-next-line max-len
-                dangerouslySetInnerHTML={{ __html: t('security_setting.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('App settings')}<i class="icon-login"></i></a>` }) }}
-              />
-            </div>
+            <p className="help-block small">{t('security_setting.desc_of_callback_URL', { AuthName: 'SAML Identity' })}</p>
+            {!adminGeneralSecurityContainer.state.appSiteUrl && (
+              <div className="alert alert-danger">
+                <i
+                  className="icon-exclamation"
+                  // eslint-disable-next-line max-len
+                  dangerouslySetInnerHTML={{ __html: t('security_setting.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('App settings')}<i class="icon-login"></i></a>` }) }}
+                />
+              </div>
             )}
           </div>
         </div>
@@ -73,14 +136,14 @@ class SamlSecurityManagement extends React.Component {
           <React.Fragment>
 
             {(adminSamlSecurityContainer.state.missingMandatoryConfigKeys.length !== 0) && (
-            <div className="alert alert-danger">
-              { t('security_setting.missing mandatory configs') }
-              <ul>
-                {/* TODO GW-583 show li after fetch data */}
-                {/* <li>{ t('security_setting.form_item_name.key') }</li> */}
-              </ul>
-            </div>
-          )}
+              <div className="alert alert-danger">
+                {t('security_setting.missing mandatory configs')}
+                <ul>
+                  {/* TODO GW-750 show li after fetch data */}
+                  {/* <li>{ t('security_setting.form_item_name.key') }</li> */}
+                </ul>
+              </div>
+            )}
 
 
             <h3 className="alert-anchor border-bottom">
@@ -98,22 +161,22 @@ class SamlSecurityManagement extends React.Component {
               </thead>
               <tbody>
                 <tr>
-                  <th>{ t('security_setting.form_item_name.entryPoint') }</th>
+                  <th>{t('security_setting.form_item_name.entryPoint')}</th>
                   <td>
                     <input
                       className="form-control"
                       type="text"
-                      name="samlDbEntryPoint"
+                      name="samlEntryPoint"
                       readOnly={useOnlyEnvVars}
-                      value={adminSamlSecurityContainer.state.samlDbEntryPoint}
-                      onChange={e => adminSamlSecurityContainer.changeSamlDbEntryPoint(e.target.value)}
+                      defaultValue={adminSamlSecurityContainer.state.samlEntryPoint}
+                      onChange={e => adminSamlSecurityContainer.changeSamlEntryPoint(e.target.value)}
                     />
                   </td>
                   <td>
                     <input
                       className="form-control"
                       type="text"
-                      value={adminSamlSecurityContainer.state.samlEnvVarEntryPoint}
+                      value={this.state.envEntryPoint}
                       readOnly
                     />
                     <p className="help-block">
@@ -122,22 +185,22 @@ class SamlSecurityManagement extends React.Component {
                   </td>
                 </tr>
                 <tr>
-                  <th>{ t('security_setting.form_item_name.issuer') }</th>
+                  <th>{t('security_setting.form_item_name.issuer')}</th>
                   <td>
                     <input
                       className="form-control"
                       type="text"
                       name="samlEnvVarissuer"
                       readOnly={useOnlyEnvVars}
-                      value={adminSamlSecurityContainer.state.samlDbIssuer}
-                      onChange={e => adminSamlSecurityContainer.changeSamlDbIssuer(e.target.value)}
+                      defaultValue={adminSamlSecurityContainer.state.samlIssuer}
+                      onChange={e => adminSamlSecurityContainer.changeSamlIssuer(e.target.value)}
                     />
                   </td>
                   <td>
                     <input
                       className="form-control"
                       type="text"
-                      value={adminSamlSecurityContainer.state.samlEnvVarIssuer}
+                      value={this.state.envIssuer}
                       readOnly
                     />
                     <p className="help-block">
@@ -146,25 +209,25 @@ class SamlSecurityManagement extends React.Component {
                   </td>
                 </tr>
                 <tr>
-                  <th>{ t('security_setting.form_item_name.cert') }</th>
+                  <th>{t('security_setting.form_item_name.cert')}</th>
                   <td>
                     <textarea
                       className="form-control input-sm"
                       type="text"
                       rows="5"
-                      name="samlDbCert"
+                      name="samlCert"
                       readOnly={useOnlyEnvVars}
-                      value={adminSamlSecurityContainer.state.samlDbcert}
-                      onChange={e => adminSamlSecurityContainer.changeSamlDbCert(e.target.value)}
+                      defaultValue={adminSamlSecurityContainer.state.samlcert}
+                      onChange={e => adminSamlSecurityContainer.changeSamlCert(e.target.value)}
                     />
                     <p className="help-block">
                       <small>
-                        { t('security_setting.SAML.cert_detail') }
+                        {t('security_setting.SAML.cert_detail')}
                       </small>
                     </p>
                     <div>
                       <small>
-                      e.g.
+                        e.g.
                         <pre>{`-----BEGIN CERTIFICATE-----
 MIICBzCCAXACCQD4US7+0A/b/zANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJK
 UDEOMAwGA1UECAwFVG9reW8xFTATBgNVBAoMDFdFU0VFSywgSW5jLjESMBAGA1UE
@@ -183,7 +246,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                       type="text"
                       rows="5"
                       readOnly
-                      value={adminSamlSecurityContainer.state.samlEnvVarCert}
+                      value={this.state.envCert}
                     />
                     <p className="help-block">
                       <small dangerouslySetInnerHTML={{ __html: t('security_setting.SAML.Use env var if empty', { env: 'SAML_CERT' }) }} />
@@ -208,18 +271,18 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
               </thead>
               <tbody>
                 <tr>
-                  <th>{ t('security_setting.form_item_name.attrMapId') }</th>
+                  <th>{t('security_setting.form_item_name.attrMapId')}</th>
                   <td>
                     <input
                       className="form-control"
                       type="text"
                       readOnly={useOnlyEnvVars}
-                      value={adminSamlSecurityContainer.state.samlDbAttrMapId}
-                      onChange={e => adminSamlSecurityContainer.changeSamlDbAttrMapId(e.target.value)}
+                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapId}
+                      onChange={e => adminSamlSecurityContainer.changeSamlAttrMapId(e.target.value)}
                     />
                     <p className="help-block">
                       <small>
-                        { t('security_setting.SAML.id_detail') }
+                        {t('security_setting.SAML.id_detail')}
                       </small>
                     </p>
                   </td>
@@ -227,7 +290,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                       className="form-control"
                       type="text"
-                      value={adminSamlSecurityContainer.state.samlEnvVarAttrMapId}
+                      value={this.state.envAttrMapId}
                       readOnly
                     />
                     <p className="help-block">
@@ -236,14 +299,14 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                   </td>
                 </tr>
                 <tr>
-                  <th>{ t('security_setting.form_item_name.attrMapUsername') }</th>
+                  <th>{t('security_setting.form_item_name.attrMapUsername')}</th>
                   <td>
                     <input
                       className="form-control"
                       type="text"
                       readOnly={useOnlyEnvVars}
-                      value={adminSamlSecurityContainer.state.samlDbAttrMapUserName}
-                      onChange={e => adminSamlSecurityContainer.changeSamlDbAttrMapUserName(e.target.value)}
+                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapUserName}
+                      onChange={e => adminSamlSecurityContainer.changeSamlAttrMapUserName(e.target.value)}
                     />
                     <p className="help-block">
                       <small dangerouslySetInnerHTML={{ __html: t('security_setting.SAML.username_detail') }} />
@@ -253,7 +316,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                       className="form-control"
                       type="text"
-                      value={adminSamlSecurityContainer.state.samlEnvVarAttrMapUserName}
+                      value={this.state.envAttrMapUserName}
                       readOnly
                     />
                     <p className="help-block">
@@ -262,14 +325,14 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                   </td>
                 </tr>
                 <tr>
-                  <th>{ t('security_setting.form_item_name.attrMapMail') }</th>
+                  <th>{t('security_setting.form_item_name.attrMapMail')}</th>
                   <td>
                     <input
                       className="form-control"
                       type="text"
                       readOnly={useOnlyEnvVars}
-                      value={adminSamlSecurityContainer.state.samlDbAttrMapMail}
-                      onChange={e => adminSamlSecurityContainer.changeSamlDbAttrMapMail(e.target.value)}
+                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapMail}
+                      onChange={e => adminSamlSecurityContainer.changeSamlAttrMapMail(e.target.value)}
                     />
                     <p className="help-block">
                       <small dangerouslySetInnerHTML={{ __html: t('security_setting.SAML.mapping_detail', { target: 'Email' }) }} />
@@ -279,7 +342,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                       className="form-control"
                       type="text"
-                      value={adminSamlSecurityContainer.state.samlEnvVarAttrMapMail}
+                      value={this.state.envAttrMapMail}
                       readOnly
                     />
                     <p className="help-block">
@@ -288,14 +351,14 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                   </td>
                 </tr>
                 <tr>
-                  <th>{ t('security_setting.form_item_name.attrMapFirstName') }</th>
+                  <th>{t('security_setting.form_item_name.attrMapFirstName')}</th>
                   <td>
                     <input
                       className="form-control"
                       type="text"
                       readOnly={useOnlyEnvVars}
-                      value={adminSamlSecurityContainer.state.samlDbAttrMapFirstName}
-                      onChange={e => adminSamlSecurityContainer.changeSamlDbAttrMapFirstName(e.target.value)}
+                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapFirstName}
+                      onChange={e => adminSamlSecurityContainer.changeSamlAttrMapFirstName(e.target.value)}
                     />
                     <p className="help-block">
                       {/* eslint-disable-next-line max-len */}
@@ -306,7 +369,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                       className="form-control"
                       type="text"
-                      value={adminSamlSecurityContainer.state.samlEnvVarAttrMapFirstName}
+                      value={this.state.envAttrMapFirstName}
                       readOnly
                     />
                     <p className="help-block">
@@ -319,14 +382,14 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                   </td>
                 </tr>
                 <tr>
-                  <th>{ t('security_setting.form_item_name.attrMapLastName') }</th>
+                  <th>{t('security_setting.form_item_name.attrMapLastName')}</th>
                   <td>
                     <input
                       className="form-control"
                       type="text"
                       readOnly={useOnlyEnvVars}
-                      value={adminSamlSecurityContainer.state.samlDbAttrMapLastName}
-                      onChange={e => adminSamlSecurityContainer.changeSamlDbAttrMapLastName(e.target.value)}
+                      defaultValue={adminSamlSecurityContainer.state.samlAttrMapLastName}
+                      onChange={e => adminSamlSecurityContainer.changeSamlAttrMapLastName(e.target.value)}
                     />
                     <p className="help-block">
                       {/* eslint-disable-next-line max-len */}
@@ -337,7 +400,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                     <input
                       className="form-control"
                       type="text"
-                      value={adminSamlSecurityContainer.state.samlEnvVarAttrMapLastName}
+                      value={this.state.envAttrMapLastName}
                       readOnly
                     />
                     <p className="help-block">
@@ -400,6 +463,12 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
 
         )}
 
+        <div className="row my-3">
+          <div className="col-xs-offset-3 col-xs-5">
+            <button type="button" className="btn btn-primary" disabled={this.state.retrieveError != null} onClick={this.onClickSubmit}>{t('Update')}</button>
+          </div>
+        </div>
+
 
       </React.Fragment>
     );

+ 90 - 48
src/client/js/services/AdminSamlSecurityContainer.js

@@ -1,6 +1,9 @@
 import { Container } from 'unstated';
 
 import loggerFactory from '@alias/logger';
+import { pathUtils } from 'growi-commons';
+
+import urljoin from 'url-join';
 
 // eslint-disable-next-line no-unused-vars
 const logger = loggerFactory('growi:security:AdminSamlSecurityContainer');
@@ -17,40 +20,44 @@ export default class AdminSamlSecurityContainer extends Container {
     this.appContainer = appContainer;
 
     this.state = {
-      // TODO GW-583 set value
       useOnlyEnvVars: false,
-      appSiteUrl: false,
-      callbackUrl: 'hoge.com',
+      callbackUrl: urljoin(pathUtils.removeTrailingSlash(appContainer.config.crowi.url), '/passport/saml/callback'),
       missingMandatoryConfigKeys: [],
-      samlDbEntryPoint: '',
-      samlEnvVarEntryPoint: '',
-      samlDbIssuer: '',
-      samlEnvVarIssuer: '',
-      samlDbCert: '',
-      samlEnvVarCert: '',
-      samlDbAttrMapId: '',
-      samlEnvVarAttrMapId: '',
-      samlDbAttrMapUserName: '',
-      samlEnvVarAttrMapUserName: '',
-      samlDbAttrMapMail: '',
-      samlEnvVarAttrMapMail: '',
-      samlDbAttrMapFirstName: '',
-      samlEnvVarAttrMapFirstName: '',
-      samlDbAttrMapLastName: '',
-      samlEnvVarAttrMapLastName: '',
+      samlEntryPoint: '',
+      samlIssuer: '',
+      samlCert: '',
+      samlAttrMapId: '',
+      samlAttrMapUserName: '',
+      samlAttrMapMail: '',
+      samlAttrMapFirstName: '',
+      samlAttrMapLastName: '',
       isSameUsernameTreatedAsIdenticalUser: false,
       isSameEmailTreatedAsIdenticalUser: false,
     };
 
-    this.init();
-
   }
 
-  init() {
-    // TODO GW-583 fetch config value with api
+  /**
+   * retrieve security data
+   */
+  async retrieveSecurityData() {
+    const response = await this.appContainer.apiv3.get('/security-setting/');
+    const { samlAuth } = response.data.securityParams;
+    this.setState({
+      samlEntryPoint: samlAuth.samlEntryPoint || '',
+      samlIssuer: samlAuth.samlIssuer || '',
+      samlCert: samlAuth.samlCert || '',
+      samlAttrMapId: samlAuth.samlAttrMapId || '',
+      samlAttrMapUserName: samlAuth.samlAttrMapUserName || '',
+      samlAttrMapMail: samlAuth.samlAttrMapMail || '',
+      samlAttrMapFirstName: samlAuth.samlAttrMapFirstName || '',
+      samlAttrMapLastName: samlAuth.samlAttrMapLastName || '',
+      isSameUsernameTreatedAsIdenticalUser: samlAuth.isSameUsernameTreatedAsIdenticalUser || false,
+      isSameEmailTreatedAsIdenticalUser: samlAuth.isSameEmailTreatedAsIdenticalUser || false,
+    });
+    return samlAuth;
   }
 
-
   /**
    * Workaround for the mangling in production build to break constructor.name
    */
@@ -59,59 +66,59 @@ export default class AdminSamlSecurityContainer extends Container {
   }
 
   /**
-   * Change samlDbEntryPoint
+   * Change samlEntryPoint
    */
-  changeSamlDbEntryPoint(inputValue) {
-    this.setState({ samlDbEntryPoint: inputValue });
+  changeSamlEntryPoint(inputValue) {
+    this.setState({ samlEntryPoint: inputValue });
   }
 
   /**
-   * Change samlDbIssuer
+   * Change samlIssuer
    */
-  changeSamlDbIssuer(inputValue) {
-    this.setState({ samlDbIssuer: inputValue });
+  changeSamlIssuer(inputValue) {
+    this.setState({ samlIssuer: inputValue });
   }
 
   /**
-   * Change samlDbCert
+   * Change samlCert
    */
-  changeSamlDbCert(inputValue) {
-    this.setState({ samlDbCert: inputValue });
+  changeSamlCert(inputValue) {
+    this.setState({ samlCert: inputValue });
   }
 
   /**
-   * Change samlDbAttrMapId
+   * Change samlAttrMapId
    */
-  changeSamlDbAttrMapId(inputValue) {
-    this.setState({ samlDbAttrMapId: inputValue });
+  changeSamlAttrMapId(inputValue) {
+    this.setState({ samlAttrMapId: inputValue });
   }
 
   /**
-   * Change samlDbAttrMapUserName
+   * Change samlAttrMapUserName
    */
-  changeSamlDbAttrMapUserName(inputValue) {
-    this.setState({ samlDbAttrMapUserName: inputValue });
+  changeSamlAttrMapUserName(inputValue) {
+    this.setState({ samlAttrMapUserName: inputValue });
   }
 
   /**
-   * Change samlDbAttrMapMail
+   * Change samlAttrMapMail
    */
-  changeSamlDbAttrMapMail(inputValue) {
-    this.setState({ samlDbAttrMapMail: inputValue });
+  changeSamlAttrMapMail(inputValue) {
+    this.setState({ samlAttrMapMail: inputValue });
   }
 
   /**
-   * Change samlDbAttrMapFirstName
+   * Change samlAttrMapFirstName
    */
-  changeSamlDbAttrMapFirstName(inputValue) {
-    this.setState({ samlDbAttrMapFirstName: inputValue });
+  changeSamlAttrMapFirstName(inputValue) {
+    this.setState({ samlAttrMapFirstName: inputValue });
   }
 
   /**
-   * Change samlDbAttrMapLastName
+   * Change samlAttrMapLastName
    */
-  changeSamlDbAttrMapLastName(inputValue) {
-    this.setState({ samlDbAttrMapLastName: inputValue });
+  changeSamlAttrMapLastName(inputValue) {
+    this.setState({ samlAttrMapLastName: inputValue });
   }
 
   /**
@@ -128,4 +135,39 @@ export default class AdminSamlSecurityContainer extends Container {
     this.setState({ isSameEmailTreatedAsIdenticalUser: !this.state.isSameEmailTreatedAsIdenticalUser });
   }
 
+  /**
+   * Update saml option
+   */
+  async updateSamlSetting() {
+
+    const response = await this.appContainer.apiv3.put('/security-setting/saml', {
+      samlEntryPoint: this.state.samlEntryPoint,
+      samlIssuer: this.state.samlIssuer,
+      samlCert: this.state.samlCert,
+      samlAttrMapId: this.state.samlAttrMapId,
+      samlAttrMapUserName: this.state.samlAttrMapUserName,
+      samlAttrMapMail: this.state.samlAttrMapMail,
+      samlAttrMapFirstName: this.state.samlAttrMapFirstName,
+      samlAttrMapLastName: this.state.samlAttrMapLastName,
+      isSameUsernameTreatedAsIdenticalUser: this.state.isSameUsernameTreatedAsIdenticalUser,
+      isSameEmailTreatedAsIdenticalUser: this.state.isSameEmailTreatedAsIdenticalUser,
+    });
+
+    const { securitySettingParams } = response.data;
+
+    this.setState({
+      samlEntryPoint: securitySettingParams.samlEntryPoint || '',
+      samlIssuer: securitySettingParams.samlIssuer || '',
+      samlCert: securitySettingParams.samlCert || '',
+      samlAttrMapId: securitySettingParams.samlAttrMapId || '',
+      samlAttrMapUserName: securitySettingParams.samlAttrMapUserName || '',
+      samlAttrMapMail: securitySettingParams.samlAttrMapMail || '',
+      samlAttrMapFirstName: securitySettingParams.samlAttrMapFirstName || '',
+      samlAttrMapLastName: securitySettingParams.samlAttrMapLastName || '',
+      isSameUsernameTreatedAsIdenticalUser: securitySettingParams.isSameUsernameTreatedAsIdenticalUser || false,
+      isSameEmailTreatedAsIdenticalUser: securitySettingParams.isSameEmailTreatedAsIdenticalUser || false,
+    });
+    return response;
+  }
+
 }

+ 123 - 0
src/server/routes/apiv3/security-setting.js

@@ -19,6 +19,18 @@ const validator = {
     body('hideRestrictedByOwner').isBoolean(),
     body('hideRestrictedByGroup').isBoolean(),
   ],
+  samlAuth: [
+    body('samlEntryPoint').isString(),
+    body('samlIssuer').isString(),
+    body('samlCert').isString(),
+    body('samlAttrMapId').isString(),
+    body('samlAttrMapUserName').isString(),
+    body('samlAttrMapMail').isString(),
+    body('samlAttrMapFirstName').isString(),
+    body('samlAttrMapLastName').isString(),
+    body('isSameUsernameTreatedAsIdenticalUser').isBoolean(),
+    body('isSameEmailTreatedAsIdenticalUser').isBoolean(),
+  ],
   oidcAuth: [
     body('oidcProviderName').isString(),
     body('oidcIssuerHost').isString(),
@@ -87,6 +99,38 @@ const validator = {
  *                  hideRestrictedByGroup:
  *                    type: boolean
  *                    description: enable hide by group
+ *          SamlAuthSetting:
+ *            type:object
+ *              samlEntryPoint:
+ *                type: string
+ *                description: entry point for saml
+ *              samlIssuer:
+ *                type: string
+ *                description: issuer for saml
+ *              samlCert:
+ *                type: string
+ *                description: certificate for saml
+ *              samlAttrMapId:
+ *                type: string
+ *                description: attribute mapping id for saml
+ *              samlAttrMapUserName:
+ *                type: string
+ *                description: attribute mapping user name for saml
+ *              samlAttrMapMail:
+ *                type: string
+ *                description: attribute mapping mail for saml
+ *              samlAttrMapFirstName:
+ *                type: string
+ *                description: attribute mapping first name for saml
+ *              samlAttrMapLastName:
+ *                type: string
+ *                description: attribute mapping last name for saml
+ *              isSameUsernameTreatedAsIdenticalUser
+ *                type: boolean
+ *                description: local account automatically linked the user name matched
+ *              isSameEmailTreatedAsIdenticalUser
+ *                type: boolean
+ *                description: local account automatically linked the email matched
  *          OidcAuthSetting:
  *            type:object
  *              oidcProviderName:
@@ -186,12 +230,33 @@ module.exports = (crowi) => {
 
     const securityParams = {
       generalAuth: {
+        isSamlEnabled: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isEnabled'),
         isOidcEnabled: await crowi.configManager.getConfig('crowi', 'security:passport-oidc:isEnabled'),
         isBasicEnabled: await crowi.configManager.getConfig('crowi', 'security:passport-basic:isEnabled'),
         isGoogleOAuthEnabled: await crowi.configManager.getConfig('crowi', 'security:passport-google:isEnabled'),
         isGithubOAuthEnabled: await crowi.configManager.getConfig('crowi', 'security:passport-github:isEnabled'),
         isTwitterOAuthEnabled: await crowi.configManager.getConfig('crowi', 'security:passport-twitter:isEnabled'),
       },
+      samlAuth: {
+        samlEntryPoint: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:entryPoint'),
+        samlEnvVarEntryPoint: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:entryPoint'),
+        samlIssuer: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:issuer'),
+        samlEnvVarIssuer: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:issuer'),
+        samlCert: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:cert'),
+        samlEnvVarCert: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:cert'),
+        samlAttrMapId: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapId'),
+        samlEnvVarAttrMapId: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapId'),
+        samlAttrMapUserName: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapUsername'),
+        samlEnvVarAttrMapUserName: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapUsername'),
+        samlAttrMapMail: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapMail'),
+        samlEnvVarAttrMapMail: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapMail'),
+        samlAttrMapFirstName: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapFirstName'),
+        samlEnvVarAttrMapFirstName: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapFirstName'),
+        samlAttrMapLastName: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapLastName'),
+        samlEnvVarAttrMapLastName: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapLastName'),
+        isSameUsernameTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
+        isSameEmailTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
+      },
       oidcAuth: {
         oidcProviderName: await crowi.configManager.getConfig('crowi', 'security:passport-oidc:providerName'),
         oidcIssuerHost: await crowi.configManager.getConfig('crowi', 'security:passport-oidc:issuerHost'),
@@ -288,6 +353,64 @@ module.exports = (crowi) => {
     }
   });
 
+  /**
+   * @swagger
+   *
+   *    /security-setting/saml:
+   *      put:
+   *        tags: [SecuritySetting]
+   *        description: Update SAML setting
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                $ref: '#/components/schemas/SecurityParams/SamlAuthSetting'
+   *        responses:
+   *          200:
+   *            description: Succeeded to update SAML setting
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  $ref: '#/components/schemas/SecurityParams/SamlAuthSetting'
+   */
+  router.put('/saml', loginRequiredStrictly, adminRequired, csrf, validator.samlAuth, ApiV3FormValidator, async(req, res) => {
+    const requestParams = {
+      'security:passport-saml:entryPoint': req.body.samlEntryPoint,
+      'security:passport-saml:issuer': req.body.samlIssuer,
+      'security:passport-saml:cert': req.body.samlCert,
+      'security:passport-saml:attrMapId': req.body.samlAttrMapId,
+      'security:passport-saml:attrMapUsername': req.body.samlAttrMapUserName,
+      'security:passport-saml:attrMapMail': req.body.samlAttrMapMail,
+      'security:passport-saml:attrMapFirstName': req.body.samlAttrMapFirstName,
+      'security:passport-saml:attrMapLastName': req.body.samlAttrMapLastName,
+      'security:passport-saml:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser,
+      'security:passport-saml:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
+    };
+
+    try {
+      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      const securitySettingParams = {
+        samlEntryPoint: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:entryPoint'),
+        samlIssuer: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:issuer'),
+        samlCert: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:cert'),
+        samlAttrMapId: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapId'),
+        samlAttrMapUserName: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapUsername'),
+        samlAttrMapMail: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapMail'),
+        samlAttrMapFirstName: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapFirstName'),
+        samlAttrMapLastName: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapLastName'),
+        isSameUsernameTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
+        isSameEmailTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
+      };
+      return res.apiv3({ securitySettingParams });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating SAML setting';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-SAML-failed'));
+    }
+  });
+
   /**
    * @swagger
    *