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

Merge pull request #1694 from weseek/reactify-admin/add-saml-new-rule

Reactify admin/add saml new rule
Yuki Takei 6 лет назад
Родитель
Сommit
39aec04589

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

@@ -124,8 +124,9 @@
   "Deleted Pages": "Deleted Pages",
   "Deleted Pages": "Deleted Pages",
   "Sign out": "Logout",
   "Sign out": "Logout",
   "form_validation": {
   "form_validation": {
-    "required": "<code>%s</code> is required",
-    "invalid_syntax": "The syntax of <code>%s</code> is invalid."
+    "error_message": "Some values ​​are incorrect",
+    "required": "%s is required",
+    "invalid_syntax": "The syntax of %s is invalid."
   },
   },
   "installer": {
   "installer": {
     "setup": "Setup",
     "setup": "Setup",

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

@@ -123,8 +123,9 @@
   "Deleted Pages": "削除済みページ",
   "Deleted Pages": "削除済みページ",
   "Sign out": "ログアウト",
   "Sign out": "ログアウト",
   "form_validation": {
   "form_validation": {
-    "required": "<code>%s</code> に値を入力してください",
-    "invalid_syntax": "<code>%s</code> の構文が不正です"
+    "error_message": "いくつかの値が設定されていません",
+    "required": "%sに値を入力してください",
+    "invalid_syntax": "%sの構文が不正です"
   },
   },
   "installer": {
   "installer": {
     "setup": "セットアップ",
     "setup": "セットアップ",

+ 54 - 0
src/client/js/components/Admin/Security/SamlSecuritySetting.jsx

@@ -25,6 +25,7 @@ class SamlSecurityManagement extends React.Component {
       envAttrMapMail: '',
       envAttrMapMail: '',
       envAttrMapFirstName: '',
       envAttrMapFirstName: '',
       envAttrMapLastName: '',
       envAttrMapLastName: '',
+      envABLCRule: '',
     };
     };
 
 
     this.onClickSubmit = this.onClickSubmit.bind(this);
     this.onClickSubmit = this.onClickSubmit.bind(this);
@@ -453,6 +454,59 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
               </div>
               </div>
             </div>
             </div>
 
 
+            <h3 className="alert-anchor border-bottom">
+              Attribute-based Login Control
+            </h3>
+
+            <p className="help-block">
+              <small dangerouslySetInnerHTML={{ __html: t('security_setting.SAML.attr_based_login_control_detail') }} />
+            </p>
+
+            <table className="table settings-table {% if useOnlyEnvVars %}use-only-env-vars{% endif %}">
+              <colgroup>
+                <col className="item-name" />
+                <col className="from-db" />
+                <col className="from-env-vars" />
+              </colgroup>
+              <thead>
+                <tr><th></th><th>Database</th><th>Environment variables</th></tr>
+              </thead>
+              <tbody>
+                <tr>
+                  <th>
+                    { t('security_setting.form_item_name.ABLCRule') }
+                  </th>
+                  <td>
+                    <input
+                      className="form-control"
+                      type="text"
+                      value={adminSamlSecurityContainer.state.samlABLCRule || ''}
+                      onChange={(e) => { adminSamlSecurityContainer.changeSamlABLCRule(e.target.value) }}
+                      readOnly={useOnlyEnvVars}
+                    />
+                    <p className="help-block">
+                      <small>
+                        <span dangerouslySetInnerHTML={{ __html: t('security_setting.SAML.attr_based_login_control_rule_detail') }} />
+                        <br />
+                        <span dangerouslySetInnerHTML={{ __html: t('security_setting.SAML.attr_based_login_control_rule_example') }} />
+                      </small>
+                    </p>
+                  </td>
+                  <td>
+                    <input
+                      className="form-control"
+                      type="text"
+                      value={this.state.envABLCRule || ''}
+                      readOnly
+                    />
+                    <p className="help-block">
+                      <small dangerouslySetInnerHTML={{ __html: t('security_setting.SAML.Use env var if empty', { env: 'SAML_ABLC_RULE' }) }} />
+                    </p>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+
           </React.Fragment>
           </React.Fragment>
 
 
         )}
         )}

+ 21 - 14
src/client/js/services/AdminSamlSecurityContainer.js

@@ -34,6 +34,7 @@ export default class AdminSamlSecurityContainer extends Container {
       samlAttrMapLastName: '',
       samlAttrMapLastName: '',
       isSameUsernameTreatedAsIdenticalUser: false,
       isSameUsernameTreatedAsIdenticalUser: false,
       isSameEmailTreatedAsIdenticalUser: false,
       isSameEmailTreatedAsIdenticalUser: false,
+      samlABLCRule: '',
     };
     };
 
 
   }
   }
@@ -57,6 +58,7 @@ export default class AdminSamlSecurityContainer extends Container {
         samlAttrMapLastName: samlAuth.samlAttrMapLastName,
         samlAttrMapLastName: samlAuth.samlAttrMapLastName,
         isSameUsernameTreatedAsIdenticalUser: samlAuth.isSameUsernameTreatedAsIdenticalUser,
         isSameUsernameTreatedAsIdenticalUser: samlAuth.isSameUsernameTreatedAsIdenticalUser,
         isSameEmailTreatedAsIdenticalUser: samlAuth.isSameEmailTreatedAsIdenticalUser,
         isSameEmailTreatedAsIdenticalUser: samlAuth.isSameEmailTreatedAsIdenticalUser,
+        samlABLCRule: samlAuth.samlABLCRule,
       });
       });
       return samlAuth;
       return samlAuth;
     }
     }
@@ -144,26 +146,30 @@ export default class AdminSamlSecurityContainer extends Container {
     this.setState({ isSameEmailTreatedAsIdenticalUser: !this.state.isSameEmailTreatedAsIdenticalUser });
     this.setState({ isSameEmailTreatedAsIdenticalUser: !this.state.isSameEmailTreatedAsIdenticalUser });
   }
   }
 
 
+  /**
+   * Change samlABLCRule
+   */
+  changeSamlABLCRule(inputValue) {
+    this.setState({ samlABLCRule: inputValue });
+  }
+
   /**
   /**
    * Update saml option
    * Update saml option
    */
    */
   async updateSamlSetting() {
   async updateSamlSetting() {
-    const {
-      samlEntryPoint, samlIssuer, samlCert, samlAttrMapId, samlAttrMapUserName, samlAttrMapMail,
-      samlAttrMapFirstName, samlAttrMapLastName, isSameUsernameTreatedAsIdenticalUser, isSameEmailTreatedAsIdenticalUser,
-    } = this.state;
 
 
     let requestParams = {
     let requestParams = {
-      samlEntryPoint,
-      samlIssuer,
-      samlCert,
-      samlAttrMapId,
-      samlAttrMapUserName,
-      samlAttrMapMail,
-      samlAttrMapFirstName,
-      samlAttrMapLastName,
-      isSameUsernameTreatedAsIdenticalUser,
-      isSameEmailTreatedAsIdenticalUser,
+      entryPoint: this.state.samlEntryPoint,
+      issuer: this.state.samlIssuer,
+      cert: this.state.samlCert,
+      attrMapId: this.state.samlAttrMapId,
+      attrMapUsername: this.state.samlAttrMapUserName,
+      attrMapMail: this.state.samlAttrMapMail,
+      attrMapFirstName: this.state.samlAttrMapFirstName,
+      attrMapLastName: this.state.samlAttrMapLastName,
+      isSameUsernameTreatedAsIdenticalUser: this.state.isSameUsernameTreatedAsIdenticalUser,
+      isSameEmailTreatedAsIdenticalUser: this.state.isSameEmailTreatedAsIdenticalUser,
+      ABLCRule: this.state.ABLCRule,
     };
     };
 
 
     requestParams = await removeNullPropertyFromObject(requestParams);
     requestParams = await removeNullPropertyFromObject(requestParams);
@@ -182,6 +188,7 @@ export default class AdminSamlSecurityContainer extends Container {
       samlAttrMapLastName: securitySettingParams.samlAttrMapLastName,
       samlAttrMapLastName: securitySettingParams.samlAttrMapLastName,
       isSameUsernameTreatedAsIdenticalUser: securitySettingParams.isSameUsernameTreatedAsIdenticalUser,
       isSameUsernameTreatedAsIdenticalUser: securitySettingParams.isSameUsernameTreatedAsIdenticalUser,
       isSameEmailTreatedAsIdenticalUser: securitySettingParams.isSameEmailTreatedAsIdenticalUser,
       isSameEmailTreatedAsIdenticalUser: securitySettingParams.isSameEmailTreatedAsIdenticalUser,
+      samlABLCRule: securitySettingParams.samlABLCRule,
     });
     });
     return response;
     return response;
   }
   }

+ 0 - 18
src/server/form/admin/securityPassportSaml.js

@@ -1,18 +0,0 @@
-const form = require('express-form');
-
-const field = form.field;
-
-module.exports = form(
-  field('settingForm[security:passport-saml:isEnabled]').trim().toBooleanStrict(),
-  field('settingForm[security:passport-saml:entryPoint]').trim().isUrl(),
-  field('settingForm[security:passport-saml:issuer]').trim(),
-  field('settingForm[security:passport-saml:attrMapId]').trim(),
-  field('settingForm[security:passport-saml:attrMapUsername]').trim(),
-  field('settingForm[security:passport-saml:attrMapMail]').trim(),
-  field('settingForm[security:passport-saml:attrMapFirstName]').trim(),
-  field('settingForm[security:passport-saml:attrMapLastName]').trim(),
-  field('settingForm[security:passport-saml:cert]').trim(),
-  field('settingForm[security:passport-saml:isSameUsernameTreatedAsIdenticalUser]').trim().toBooleanStrict(),
-  field('settingForm[security:passport-saml:isSameEmailTreatedAsIdenticalUser]').trim().toBooleanStrict(),
-  field('settingForm[security:passport-saml:ABLCRule]').trim(),
-);

+ 0 - 24
src/server/routes/admin.js

@@ -559,29 +559,5 @@ module.exports = function(crowi, app) {
     return res.json(ApiResponse.success());
     return res.json(ApiResponse.success());
   };
   };
 
 
-  // /**
-  //  * validate setting form values for SAML
-  //  *
-  //  * - For the value of each mandatory items,
-  //  *     check whether it from the environment variables is empty and form value to update it is empty
-  //  * - validate the syntax of a attribute-based login control rule
-  //  */
-  // function validateSamlSettingForm(form, t) {
-  //   for (const key of crowi.passportService.mandatoryConfigKeysForSaml) {
-  //     const formValue = form.settingForm[key];
-  //     if (configManager.getConfigFromEnvVars('crowi', key) === null && formValue === '') {
-  //       const formItemName = t(`security_setting.form_item_name.${key}`);
-  //       form.errors.push(t('form_validation.required', formItemName));
-  //     }
-  //   }
-
-  //   const rule = form.settingForm['security:passport-saml:ABLCRule'];
-  //   // Empty string disables attribute-based login control.
-  //   // So, when rule is empty string, validation is passed.
-  //   if (rule !== '' && (rule == null || crowi.passportService.parseABLCRule(rule) == null)) {
-  //     form.errors.push(t('form_validation.invalid_syntax', t('security_setting.form_item_name.security:passport-saml:ABLCRule')));
-  //   }
-  // }
-
   return actions;
   return actions;
 };
 };

+ 47 - 16
src/server/routes/apiv3/security-setting.js

@@ -51,16 +51,17 @@ const validator = {
     body('ldapGroupDnProperty').if((value, { req }) => req.body.ldapGroupDnProperty).isString(),
     body('ldapGroupDnProperty').if((value, { req }) => req.body.ldapGroupDnProperty).isString(),
   ],
   ],
   samlAuth: [
   samlAuth: [
-    body('samlEntryPoint').if((value, { req }) => req.body.samlEntryPoint).isString(),
-    body('samlIssuer').if((value, { req }) => req.body.samlIssuer).isString(),
-    body('samlCert').if((value, { req }) => req.body.samlCert).isString(),
-    body('samlAttrMapId').if((value, { req }) => req.body.samlAttrMapId).isString(),
-    body('samlAttrMapUserName').if((value, { req }) => req.body.samlAttrMapUserName).isString(),
-    body('samlAttrMapMail').if((value, { req }) => req.body.samlAttrMapMail).isString(),
-    body('samlAttrMapFirstName').if((value, { req }) => req.body.samlAttrMapFirstName).isString(),
-    body('samlAttrMapLastName').if((value, { req }) => req.body.samlAttrMapLastName).isString(),
+    body('entryPoint').if((value, { req }) => req.body.samlEntryPoint).isString(),
+    body('issuer').if((value, { req }) => req.body.samlIssuer).isString(),
+    body('cert').if((value, { req }) => req.body.samlCert).isString(),
+    body('attrMapId').if((value, { req }) => req.body.samlAttrMapId).isString(),
+    body('attrMapUserName').if((value, { req }) => req.body.samlAttrMapUserName).isString(),
+    body('attrMapMail').if((value, { req }) => req.body.samlAttrMapMail).isString(),
+    body('attrMapFirstName').if((value, { req }) => req.body.samlAttrMapFirstName).isString(),
+    body('attrMapLastName').if((value, { req }) => req.body.samlAttrMapLastName).isString(),
     body('isSameUsernameTreatedAsIdenticalUser').if((value, { req }) => req.body.isSameUsernameTreatedAsIdenticalUser).isBoolean(),
     body('isSameUsernameTreatedAsIdenticalUser').if((value, { req }) => req.body.isSameUsernameTreatedAsIdenticalUser).isBoolean(),
     body('isSameEmailTreatedAsIdenticalUser').if((value, { req }) => req.body.isSameEmailTreatedAsIdenticalUser).isBoolean(),
     body('isSameEmailTreatedAsIdenticalUser').if((value, { req }) => req.body.isSameEmailTreatedAsIdenticalUser).isBoolean(),
+    body('ABLCRule').if((value, { req }) => req.body.samlABLCRule).isString(),
   ],
   ],
   oidcAuth: [
   oidcAuth: [
     body('oidcProviderName').if((value, { req }) => req.body.oidcProviderName).isString(),
     body('oidcProviderName').if((value, { req }) => req.body.oidcProviderName).isString(),
@@ -207,6 +208,9 @@ const validator = {
  *          isSameEmailTreatedAsIdenticalUser:
  *          isSameEmailTreatedAsIdenticalUser:
  *            type: boolean
  *            type: boolean
  *            description: local account automatically linked the email matched
  *            description: local account automatically linked the email matched
+ *          samlABLCRule:
+ *            type: string
+ *            description: ABLCRule for saml
  *      OidcAuthSetting:
  *      OidcAuthSetting:
  *        type: object
  *        type: object
  *        properties:
  *        properties:
@@ -366,6 +370,7 @@ module.exports = (crowi) => {
         samlEnvVarAttrMapLastName: await crowi.configManager.getConfigFromEnvVars('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'),
         isSameUsernameTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
         isSameEmailTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
         isSameEmailTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
+        samlABLCRule: await crowi.configManager.getConfig('crowi', 'security:passport-saml:ABLCRule'),
       },
       },
       oidcAuth: {
       oidcAuth: {
         oidcProviderName: await crowi.configManager.getConfig('crowi', 'security:passport-oidc:providerName'),
         oidcProviderName: await crowi.configManager.getConfig('crowi', 'security:passport-oidc:providerName'),
@@ -637,17 +642,42 @@ module.exports = (crowi) => {
    *                  $ref: '#/components/schemas/SamlAuthSetting'
    *                  $ref: '#/components/schemas/SamlAuthSetting'
    */
    */
   router.put('/saml', loginRequiredStrictly, adminRequired, csrf, validator.samlAuth, ApiV3FormValidator, async(req, res) => {
   router.put('/saml', loginRequiredStrictly, adminRequired, csrf, validator.samlAuth, ApiV3FormValidator, async(req, res) => {
+
+    //  For the value of each mandatory items,
+    //  check whether it from the environment variables is empty and form value to update it is empty
+    //  validate the syntax of a attribute - based login control rule
+    const invalidValues = [];
+    for (const configKey of crowi.passportService.mandatoryConfigKeysForSaml) {
+      const key = configKey.replace('security:passport-saml:', '');
+      const formValue = req.body[key];
+      if (crowi.configManager.getConfigFromEnvVars('crowi', configKey) === null && formValue == null) {
+        const formItemName = req.t(`security_setting.form_item_name.${key}`);
+        invalidValues.push(req.t('form_validation.required', formItemName));
+      }
+    }
+    if (invalidValues.length !== 0) {
+      return res.apiv3Err(req.t('form_validation.error_message'), 400, invalidValues);
+    }
+
+    const rule = req.body.samlABLCRule;
+    // Empty string disables attribute-based login control.
+    // So, when rule is empty string, validation is passed.
+    if (rule != null && (rule == null || crowi.passportService.parseABLCRule(rule) == null)) {
+      return res.apiv3Err(req.t('form_validation.invalid_syntax', req.t('security_setting.form_item_name.ABLCRule')), 400);
+    }
+
     const requestParams = {
     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:entryPoint': req.body.entryPoint,
+      'security:passport-saml:issuer': req.body.issuer,
+      'security:passport-saml:cert': req.body.cert,
+      'security:passport-saml:attrMapId': req.body.attrMapId,
+      'security:passport-saml:attrMapUsername': req.body.attrMapUserName,
+      'security:passport-saml:attrMapMail': req.body.attrMapMail,
+      'security:passport-saml:attrMapFirstName': req.body.attrMapFirstName,
+      'security:passport-saml:attrMapLastName': req.body.attrMapLastName,
       'security:passport-saml:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser,
       'security:passport-saml:isSameUsernameTreatedAsIdenticalUser': req.body.isSameUsernameTreatedAsIdenticalUser,
       'security:passport-saml:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
       'security:passport-saml:isSameEmailTreatedAsIdenticalUser': req.body.isSameEmailTreatedAsIdenticalUser,
+      'security:passport-saml:ABLCRule': req.body.ABLCRule,
     };
     };
 
 
     try {
     try {
@@ -665,6 +695,7 @@ module.exports = (crowi) => {
         samlAttrMapLastName: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapLastName'),
         samlAttrMapLastName: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:attrMapLastName'),
         isSameUsernameTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
         isSameUsernameTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
         isSameEmailTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
         isSameEmailTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
+        samlABLCRule: await crowi.configManager.getConfig('crowi', 'security:passport-saml:ABLCRule'),
       };
       };
       return res.apiv3({ securitySettingParams });
       return res.apiv3({ securitySettingParams });
     }
     }

+ 0 - 1
src/server/service/passport.js

@@ -73,7 +73,6 @@ class PassportService {
      * the keys of mandatory configs for SAML
      * the keys of mandatory configs for SAML
      */
      */
     this.mandatoryConfigKeysForSaml = [
     this.mandatoryConfigKeysForSaml = [
-      'security:passport-saml:isEnabled',
       'security:passport-saml:entryPoint',
       'security:passport-saml:entryPoint',
       'security:passport-saml:issuer',
       'security:passport-saml:issuer',
       'security:passport-saml:cert',
       'security:passport-saml:cert',

+ 0 - 456
src/server/views/admin/widget/passport/saml.html

@@ -1,456 +0,0 @@
-<form action="/_api/admin/security/passport-saml" method="post" class="form-horizontal passportStrategy" id="samlSetting" role="form">
-  <legend class="alert-anchor">{{ t("security_setting.SAML.name") }} {{ t("security_setting.configuration") }}</legend>
-
-  {% set nameForIsSamlEnabled = "settingForm[security:passport-saml:isEnabled]" %}
-  {% set isSamlEnabled  = getConfig('crowi', 'security:passport-saml:isEnabled') %}
-  {% set useOnlyEnvVars = getConfig('crowi', 'security:passport-saml:useOnlyEnvVarsForSomeOptions') %}
-  {% set siteUrl = getConfig('crowi', 'app:siteUrl') || '[INVALID]' %}
-  {% set callbackUrl = pathUtils.removeTrailingSlash(siteUrl) + '/passport/saml/callback' %}
-
-  {% if useOnlyEnvVars %}
-    <p class="alert alert-info">
-      {{ t("security_setting.SAML.note for the only env option", "SAML_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS") }}
-    </p>
-  {% endif %}
-
-  <div class="form-group">
-    <label class="col-xs-3 control-label">{{ t("security_setting.SAML.name") }}</label>
-    <div class="col-xs-6">
-      <div class="btn-group btn-toggle {% if useOnlyEnvVars %}btn-group-disabled{% endif %}" data-toggle="buttons">
-        <label class="btn btn-default btn-rounded btn-outline {% if isSamlEnabled %}active{% endif %}" data-active-class="primary">
-          <input name="{{nameForIsSamlEnabled}}"
-                 value="true"
-                 type="radio"
-                 {% if true === isSamlEnabled %}checked{% endif %}
-                 {% if useOnlyEnvVars %}readonly{% endif %}> ON
-        </label>
-        <label class="btn btn-default btn-rounded btn-outline {% if !isSamlEnabled %}active{% endif %}" data-active-class="default">
-          <input name="{{nameForIsSamlEnabled}}"
-                 value="false"
-                 type="radio"
-                 {% if !isSamlEnabled %}checked{% endif %}
-                 {% if useOnlyEnvVars %}readonly{% endif %}> OFF
-        </label>
-      </div>
-    </div>
-  </div>
-
-  <div class="form-group">
-    <label class="col-xs-3 control-label">{{ t("security_setting.callback_URL") }}</label>
-    <div class="col-xs-6">
-      <input class="form-control"
-             type="text"
-             value="{{ callbackUrl }}"
-             readonly>
-      <p class="help-block small">{{ t("security_setting.desc_of_callback_URL", 'SAML Identity') }}</p>
-      {% if !getConfig('crowi', 'app:siteUrl') %}
-      <div class="alert alert-danger">
-        <i class="icon-exclamation"></i> {{ t("security_setting.alert_siteUrl_is_not_set", '<a href="/admin/app">' + t('App settings') + '<i class="icon-login"></i></a>') }}
-      </div>
-      {% endif %}
-    </div>
-  </div>
-
-  <fieldset id="passport-saml-hide-when-disabled" {%if !isSamlEnabled %}style="display: none;"{% endif %}>
-
-    {% set missingMandatoryConfigKeys = getSamlMissingMandatoryConfigKeys() %}
-    {% if missingMandatoryConfigKeys.length !== 0 %}
-    <div class="alert alert-danger">
-      {{ t("security_setting.missing mandatory configs") }}
-      <ul>
-        {% for missingMandatoryConfigKey in missingMandatoryConfigKeys %}
-        <li>{{ t("security_setting.form_item_name." + missingMandatoryConfigKey) }}</li>
-        {% endfor %}
-      </ul>
-    </div>
-    {% endif %}
-
-    <h4>Basic Settings</h4>
-    <table class="table settings-table {% if useOnlyEnvVars %}use-only-env-vars{% endif %}">
-      <colgroup>
-        <col class="item-name">
-        <col class="from-db">
-        <col class="from-env-vars">
-      </colgroup>
-      <thead>
-        <tr><th></th><th>Database</th><th>Environment variables</th></tr>
-      </thead>
-      <tbody>
-        <tr>
-          <th>{{ t("security_setting.form_item_name.security:passport-saml:entryPoint") }}</th>
-          <td>
-            <input class="form-control"
-                   type="text"
-                   name="settingForm[security:passport-saml:entryPoint]"
-                   value="{{ getConfigFromDB('crowi', 'security:passport-saml:entryPoint') || '' }}"
-                   {% if useOnlyEnvVars %}readonly{% endif %}>
-          </td>
-          <td>
-            <input class="form-control"
-                   type="text"
-                   value="{{ getConfigFromEnvVars('crowi', 'security:passport-saml:entryPoint') || '' }}"
-                   readonly>
-            <p class="help-block">
-              <small>
-                {{ t("security_setting.SAML.Use env var if empty", "SAML_ENTRY_POINT") }}
-              </small>
-            </p>
-          </td>
-        </tr>
-        <tr>
-          <th>{{ t("security_setting.form_item_name.security:passport-saml:issuer") }}</th>
-          <td>
-            <input class="form-control"
-                   type="text"
-                   name="settingForm[security:passport-saml:issuer]"
-                   value="{{ getConfigFromDB('crowi', 'security:passport-saml:issuer') || '' }}"
-                   {% if useOnlyEnvVars %}readonly{% endif %}>
-          </td>
-          <td>
-            <input class="form-control"
-                   type="text"
-                   value="{{ getConfigFromEnvVars('crowi', 'security:passport-saml:issuer') || '' }}"
-                   readonly>
-            <p class="help-block">
-              <small>
-                {{ t("security_setting.SAML.Use env var if empty", "SAML_ISSUER") }}
-              </small>
-            </p>
-          </td>
-        </tr>
-        <tr>
-          <th>{{ t("security_setting.form_item_name.security:passport-saml:cert") }}</th>
-          <td>
-            <textarea class="form-control input-sm"
-                      type="text"
-                      rows="5"
-                      name="settingForm[security:passport-saml:cert]"
-                      {% if useOnlyEnvVars %}readonly{% endif %}
-            >{{ getConfigFromDB('crowi', 'security:passport-saml:cert') || '' }}</textarea>
-            <p class="help-block">
-              <small>
-                {{ t("security_setting.SAML.cert_detail") }}
-              </small>
-            </p>
-            <p>
-              <small>
-                e.g.
-                <pre>-----BEGIN CERTIFICATE-----
-MIICBzCCAXACCQD4US7+0A/b/zANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJK
-UDEOMAwGA1UECAwFVG9reW8xFTATBgNVBAoMDFdFU0VFSywgSW5jLjESMBAGA1UE
-...
-crmVwBzbloUO2l6k1ibwD2WVwpdxMKIF5z58HfKAvxZAzCHE7kMEZr1ge30WRXQA
-pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
------END CERTIFICATE-----</pre>
-              </small>
-            </p>
-          </td>
-          <td>
-            <textarea class="form-control input-sm"
-                      type="text"
-                      rows="5"
-                      readonly
-            >{{ getConfigFromEnvVars('crowi', 'security:passport-saml:cert') || '' }}</textarea>
-            <p class="help-block">
-              <small>
-                {{ t("security_setting.SAML.Use env var if empty", "SAML_CERT") }}
-              </small>
-            </p>
-          </td>
-        </tr>
-      </tbody>
-    </table>
-
-    <h4>Attribute Mapping</h4>
-
-    <table class="table settings-table {% if useOnlyEnvVars %}use-only-env-vars{% endif %}">
-      <colgroup>
-        <col class="item-name">
-        <col class="from-db">
-        <col class="from-env-vars">
-      </colgroup>
-      <thead>
-        <tr><th></th><th>Database</th><th>Environment variables</th></tr>
-      </thead>
-      <tbody>
-      <tr>
-        <th>{{ t("security_setting.form_item_name.security:passport-saml:attrMapId") }}</th>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 name="settingForm[security:passport-saml:attrMapId]"
-                 value="{{ getConfigFromDB('crowi', 'security:passport-saml:attrMapId') || '' }}"
-                 {% if useOnlyEnvVars %}readonly{% endif %}>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.id_detail") }}
-            </small>
-          </p>
-        </td>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 value="{{ getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapId') || '' }}"
-                 readonly>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.Use env var if empty", "SAML_ATTR_MAPPING_ID") }}
-            </small>
-          </p>
-        </td>
-      </tr>
-      <tr>
-        <th>{{ t("security_setting.form_item_name.security:passport-saml:attrMapUsername") }}</th>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 name="settingForm[security:passport-saml:attrMapUsername]"
-                 value="{{ getConfigFromDB('crowi', 'security:passport-saml:attrMapUsername') || '' }}"
-                 {% if useOnlyEnvVars %}readonly{% endif %}>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.username_detail") }}
-            </small>
-          </p>
-        </td>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 value="{{ getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapUsername') || '' }}"
-                 readonly>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.Use env var if empty", "SAML_ATTR_MAPPING_USERNAME") }}
-            </small>
-          </p>
-        </td>
-      </tr>
-      <tr>
-        <th>{{ t("security_setting.form_item_name.security:passport-saml:attrMapMail") }}</th>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 name="settingForm[security:passport-saml:attrMapMail]"
-                 value="{{ getConfigFromDB('crowi', 'security:passport-saml:attrMapMail') || '' }}"
-                 {% if useOnlyEnvVars %}readonly{% endif %}>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.mapping_detail", t("Email")) }}
-            </small>
-        </td>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 value="{{ getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapMail') || '' }}"
-                 readonly>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.Use env var if empty", "SAML_ATTR_MAPPING_MAIL") }}
-            </small>
-          </p>
-        </td>
-      </tr>
-      <tr>
-        <th>{{ t("security_setting.form_item_name.security:passport-saml:attrMapFirstName") }}</th>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 name="settingForm[security:passport-saml:attrMapFirstName]"
-                 value="{{ getConfigFromDB('crowi', 'security:passport-saml:attrMapFirstName') || '' }}"
-                 {% if useOnlyEnvVars %}readonly{% endif %}>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.mapping_detail", t("security_setting.form_item_name.security:passport-saml:attrMapFirstName")) }}
-            </small>
-          </p>
-        </td>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 value="{{ getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapFirstName') || '' }}"
-                 readonly>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.Use env var if empty", "SAML_ATTR_MAPPING_FIRST_NAME") }}<br>
-              {{ t("security_setting.Use default if both are empty", "firstName") }}
-            </small>
-          </p>
-        </td>
-      </tr>
-      <tr>
-        <th>{{ t("security_setting.form_item_name.security:passport-saml:attrMapLastName") }}</th>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 name="settingForm[security:passport-saml:attrMapLastName]"
-                 value="{{ getConfigFromDB('crowi', 'security:passport-saml:attrMapLastName') || '' }}"
-                 {% if useOnlyEnvVars %}readonly{% endif %}>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.mapping_detail", t("security_setting.form_item_name.security:passport-saml:attrMapLastName")) }}
-            </small>
-          </p>
-        </td>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 value="{{ getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapLastName') || '' }}"
-                 readonly>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.Use env var if empty", "SAML_ATTR_MAPPING_LAST_NAME") }}<br>
-              {{ t("security_setting.Use default if both are empty", "lastName") }}
-            </small>
-          </p>
-        </td>
-      </tr>
-      </tbody>
-    </table>
-
-    <h4>Attribute Mapping Options</h4>
-
-    <div class="form-group">
-      <div class="col-xs-offset-1">
-        <div class="checkbox checkbox-info">
-          <input id="bindByUserName-SAML"
-                 type="checkbox"
-                 name="settingForm[security:passport-saml:isSameUsernameTreatedAsIdenticalUser]"
-                 value="1"
-                 {% if getConfig('crowi', 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser') %}checked{% endif %} />
-          <label for="bindByUserName-SAML">
-            {{ t("security_setting.Treat username matching as identical", "username") }}
-          </label>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.Treat username matching as identical_warn", "username") }}
-            </small>
-          </p>
-        </div>
-      </div>
-    </div>
-
-    <div class="form-group">
-      <div class="col-xs-offset-1">
-        <div class="checkbox checkbox-info">
-          <input id="bindByEmail-SAML"
-                 type="checkbox"
-                 name="settingForm[security:passport-saml:isSameEmailTreatedAsIdenticalUser]"
-                 value="1"
-                 {% if getConfig('crowi', 'security:passport-saml:isSameEmailTreatedAsIdenticalUser') %}checked{% endif %} />
-          <label for="bindByEmail-SAML">
-            {{ t("security_setting.Treat email matching as identical", "email") }}
-          </label>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.Treat email matching as identical_warn", "email") }}
-            </small>
-          </p>
-        </div>
-      </div>
-    </div>
-
-    <h4>Attribute-based Login Control</h4>
-
-    <p class="help-block">
-      <small>
-        {{ t("security_setting.SAML.attr_based_login_control_detail") }}
-      </small>
-    </p>
-
-    <table class="table settings-table {% if useOnlyEnvVars %}use-only-env-vars{% endif %}">
-      <colgroup>
-        <col class="item-name">
-        <col class="from-db">
-        <col class="from-env-vars">
-      </colgroup>
-      <thead>
-        <tr><th></th><th>Database</th><th>Environment variables</th></tr>
-      </thead>
-      <tbody>
-      <tr>
-        <th>
-          {{ t("security_setting.form_item_name.security:passport-saml:ABLCRule") }}
-        </th>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 name="settingForm[security:passport-saml:ABLCRule]"
-                 value="{{ getConfigFromDB('crowi', 'security:passport-saml:ABLCRule') || '' }}"
-                 {% if useOnlyEnvVars %}readonly{% endif %}>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.attr_based_login_control_rule_detail") }}<br>
-              {{ t("security_setting.SAML.attr_based_login_control_rule_example") }}
-            </small>
-          </p>
-        </td>
-        <td>
-          <input class="form-control"
-                 type="text"
-                 value="{{ getConfigFromEnvVars('crowi', 'security:passport-saml:ABLCRule') || '' }}"
-                 readonly>
-          <p class="help-block">
-            <small>
-              {{ t("security_setting.SAML.Use env var if empty", "SAML_ABLC_RULE") }}
-            </small>
-          </p>
-        </td>
-      </tr>
-      </tbody>
-    </table>
-
-  </fieldset>
-
-  <div class="form-group" id="btn-update">
-    <div class="col-xs-offset-3 col-xs-6">
-      <input type="hidden" name="_csrf" value="{{ csrf() }}">
-      <button type="submit" class="btn btn-primary">{{ t('Update') }}</button>
-    </div>
-  </div>
-
-</form>
-
-<script>
-  $('.btn-group-disabled').on('click', '.btn', function() {
-    return false;
-  });
-
-  $('input[name="settingForm[security:passport-saml:isEnabled]"]').change(function() {
-    const isEnabled = ($(this).val() === "true");
-
-    if (isEnabled) {
-      $('#passport-saml-hide-when-disabled').show(400);
-    }
-    else {
-      $('#passport-saml-hide-when-disabled').hide(400);
-    }
-  });
-
-
-  /**
-   * The following script sets the class name 'unused' to the cell in from-env-vars column
-   * when the value of the corresponding cell from the database is not empty.
-   * It is used to indicate that the system does not use a value from the environment variables by setting a css style.
-   * This behavior is disabled when the system is in the use-only-env-vars mode.
-   */
-  $('.settings-table:not(.use-only-env-vars) tbody tr').each(function(_, element) {
-    const inputElemFromDB      = $('td:nth-of-type(1) input[type="text"], td:nth-of-type(1) textarea', element);
-    const inputElemFromEnvVars = $('td:nth-of-type(2) input[type="text"], td:nth-of-type(2) textarea', element);
-
-    // initialize
-    addClassToUnusedInputElemFromEnvVars(inputElemFromDB, inputElemFromEnvVars);
-
-    // set keyup event handler
-    inputElemFromDB.keyup(function () { addClassToUnusedInputElemFromEnvVars(inputElemFromDB, inputElemFromEnvVars) });
-  });
-
-  function addClassToUnusedInputElemFromEnvVars(inputElemFromDB, inputElemFromEnvVars) {
-    if (inputElemFromDB.val() === '') {
-      inputElemFromEnvVars.parent().removeClass('unused');
-    }
-    else {
-      inputElemFromEnvVars.parent().addClass('unused');
-    }
-  };
-</script>
-