2
0
Эх сурвалжийг харах

Merge pull request #1137 from weseek/feat/switch-local-strategy

Feat/switch local strategy
Yuki Takei 6 жил өмнө
parent
commit
a35e0cf80e

+ 9 - 6
README.md

@@ -185,13 +185,10 @@ Environment Variables
     * BLOCKDIAG_URI: URI to connect to [blockdiag](http://http://blockdiag.com/) server.
 * **Option (Overwritable in admin page)**
     * APP_SITE_URL: Site URL. e.g. `https://example.com`, `https://example.com:8080`
-    * OAUTH_GOOGLE_CLIENT_ID: Google API client id for OAuth login.
-    * OAUTH_GOOGLE_CLIENT_SECRET: Google API client secret for OAuth login.
-    * OAUTH_GITHUB_CLIENT_ID: GitHub API client id for OAuth login.
-    * OAUTH_GITHUB_CLIENT_SECRET: GitHub API client secret for OAuth login.
-    * OAUTH_TWITTER_CONSUMER_KEY: Twitter consumer key(API key) for OAuth login.
-    * OAUTH_TWITTER_CONSUMER_SECRET: Twitter consumer secret(API secret) for OAuth login.
+    * LOCAL_STRATEGY_ENABLED: Enable or disable ID/Pass login
+    * LOCAL_STRATEGY_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS: Prioritize env vars than values in DB for some ID/Pass login options
     * SAML_ENABLED: Enable or disable SAML
+    * SAML_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS: Prioritize env vars than values in DB for some SAML options
     * SAML_ENTRY_POINT: IdP entry point
     * SAML_ISSUER: Issuer string to supply to IdP
     * SAML_ATTR_MAPPING_ID: Attribute map for id
@@ -200,6 +197,12 @@ Environment Variables
     * SAML_ATTR_MAPPING_FIRST_NAME: Attribute map for first name
     * SAML_ATTR_MAPPING_LAST_NAME:  Attribute map for last name
     * SAML_CERT: PEM-encoded X.509 signing certificate string to validate the response from IdP
+    * OAUTH_GOOGLE_CLIENT_ID: Google API client id for OAuth login.
+    * OAUTH_GOOGLE_CLIENT_SECRET: Google API client secret for OAuth login.
+    * OAUTH_GITHUB_CLIENT_ID: GitHub API client id for OAuth login.
+    * OAUTH_GITHUB_CLIENT_SECRET: GitHub API client secret for OAuth login.
+    * OAUTH_TWITTER_CONSUMER_KEY: Twitter consumer key(API key) for OAuth login.
+    * OAUTH_TWITTER_CONSUMER_SECRET: Twitter consumer secret(API secret) for OAuth login.
 
 
 Documentation

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

@@ -474,7 +474,7 @@
       "readonly": "Accept (Guests can read only)"
     },
     "registration_mode": {
-      "open": "Open (Anyone can registre)",
+      "open": "Open (Anyone can register)",
       "restricted": "Restricted (Requires approval by administrators)",
       "closed": "Closed (Invitation Only)"
     },
@@ -487,6 +487,9 @@
     "Use env var if empty": "Use env var <code>%s</code> if empty",
     "Use default if both are empty": "If both ​​are empty, the default value <code>%s</code> is used.",
     "missing mandatory configs": "The following mandatory items are not set in either database nor environment variables.",
+    "Local": {
+      "name": "ID/Password"
+    },
     "ldap": {
       "server_url_detail": "The LDAP URL of the directory service in the format <code>ldap://host:port/DN</code> or <code>ldaps://host:port/DN</code>.",
       "bind_mode": "Binding Mode",

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

@@ -481,6 +481,9 @@
     "Use env var if empty": "空の場合、環境変数 <code>%s</code> を利用します",
     "Use default if both are empty": "どちらの値も空の場合、デフォルト値 <code>%s</code> を利用します",
     "missing mandatory configs": "以下の必須項目の値がデータベースと環境変数のどちらにも設定されていません",
+    "Local": {
+      "name": "ID/Password"
+    },
     "ldap": {
       "server_url_detail": "LDAP URLを <code>ldap://host:port/DN</code> または <code>ldaps://host:port/DN</code> の形式で入力してください。",
       "bind_mode": "Bind モード",

+ 9 - 7
src/client/styles/scss/_login.scss

@@ -94,13 +94,7 @@
     }
   }
 
-  .collapse-external-auth {
-    overflow: hidden;
-    &:not(.in) {
-      height: 0;
-      padding: 0 !important;
-    }
-
+  .external-auth {
     form {
       flex: 1;
       @media (min-width: 350px) {
@@ -112,6 +106,14 @@
     }
   }
 
+  .collapse-external-auth {
+    overflow: hidden;
+    &:not(.in) {
+      height: 0;
+      padding: 0 !important;
+    }
+  }
+
   // button style
   .btn-login.fcbtn,
   .btn-register.fcbtn,

+ 1 - 1
src/server/crowi/index.js

@@ -286,8 +286,8 @@ Crowi.prototype.setupPassport = async function() {
   }
   this.passportService.setupSerializer();
   // setup strategies
-  this.passportService.setupLocalStrategy();
   try {
+    this.passportService.setupLocalStrategy();
     this.passportService.setupLdapStrategy();
     this.passportService.setupGoogleStrategy();
     this.passportService.setupGitHubStrategy();

+ 0 - 4
src/server/form/admin/securityGeneral.js

@@ -1,13 +1,9 @@
 const form = require('express-form');
 
 const field = form.field;
-const stringToArray = require('../../util/formUtil').stringToArrayFilter;
-const normalizeCRLF = require('../../util/formUtil').normalizeCRLFFilter;
 
 module.exports = form(
   field('settingForm[security:restrictGuestMode]'),
-  field('settingForm[security:registrationMode]').required(),
-  field('settingForm[security:registrationWhiteList]').custom(normalizeCRLF).custom(stringToArray),
   field('settingForm[security:list-policy:hideRestrictedByOwner]').trim().toBooleanStrict(),
   field('settingForm[security:list-policy:hideRestrictedByGroup]').trim().toBooleanStrict(),
   field('settingForm[security:pageCompleteDeletionAuthority]'),

+ 11 - 0
src/server/form/admin/securityPassportLocal.js

@@ -0,0 +1,11 @@
+const form = require('express-form');
+
+const field = form.field;
+const stringToArray = require('../../util/formUtil').stringToArrayFilter;
+const normalizeCRLF = require('../../util/formUtil').normalizeCRLFFilter;
+
+module.exports = form(
+  field('settingForm[security:passport-local:isEnabled]').trim().toBooleanStrict().required(),
+  field('settingForm[security:registrationMode]').required(),
+  field('settingForm[security:registrationWhiteList]').custom(normalizeCRLF).custom(stringToArray),
+);

+ 1 - 0
src/server/form/index.js

@@ -19,6 +19,7 @@ module.exports = {
     importerQiita: require('./admin/importerQiita'),
     plugin: require('./admin/plugin'),
     securityGeneral: require('./admin/securityGeneral'),
+    securityPassportLocal: require('./admin/securityPassportLocal'),
     securityPassportLdap: require('./admin/securityPassportLdap'),
     securityPassportSaml: require('./admin/securityPassportSaml'),
     securityPassportBasic: require('./admin/securityPassportBasic'),

+ 1 - 0
src/server/models/config.js

@@ -49,6 +49,7 @@ module.exports = function(crowi) {
       'security:list-policy:hideRestrictedByGroup' : false,
       'security:pageCompleteDeletionAuthority' : undefined,
 
+      'security:passport-local:isEnabled' : true,
       'security:passport-ldap:isEnabled' : false,
       'security:passport-ldap:serverUrl' : undefined,
       'security:passport-ldap:isUserBind' : undefined,

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

@@ -918,6 +918,32 @@ module.exports = function(crowi, app) {
     }
   };
 
+  actions.api.securityPassportLocalSetting = async function(req, res) {
+    const form = req.form.settingForm;
+
+    if (!req.form.isValid) {
+      return res.json({ status: false, message: req.form.errors.join('\n') });
+    }
+
+    debug('form content', form);
+
+    try {
+      await configManager.updateConfigsInTheSameNamespace('crowi', form);
+      // reset strategy
+      crowi.passportService.resetLocalStrategy();
+      // setup strategy
+      if (configManager.getConfig('crowi', 'security:passport-local:isEnabled')) {
+        crowi.passportService.setupLocalStrategy(true);
+      }
+    }
+    catch (err) {
+      logger.error(err);
+      return res.json({ status: false, message: err.message });
+    }
+
+    return res.json({ status: true });
+  };
+
   actions.api.securityPassportLdapSetting = async function(req, res) {
     const form = req.form.settingForm;
 

+ 1 - 0
src/server/routes/index.js

@@ -58,6 +58,7 @@ module.exports = function(crowi, app) {
   // security admin
   app.get('/admin/security'                     , loginRequired() , adminRequired , admin.security.index);
   app.post('/_api/admin/security/general'       , loginRequired() , adminRequired , form.admin.securityGeneral, admin.api.securitySetting);
+  app.post('/_api/admin/security/passport-local', loginRequired() , adminRequired , csrf, form.admin.securityPassportLocal, admin.api.securityPassportLocalSetting);
   app.post('/_api/admin/security/passport-ldap' , loginRequired() , adminRequired , csrf, form.admin.securityPassportLdap, admin.api.securityPassportLdapSetting);
   app.post('/_api/admin/security/passport-saml' , loginRequired() , adminRequired , csrf, form.admin.securityPassportSaml, admin.api.securityPassportSamlSetting);
   app.post('/_api/admin/security/passport-basic' , loginRequired() , adminRequired , csrf, form.admin.securityPassportBasic, admin.api.securityPassportBasicSetting);

+ 6 - 0
src/server/routes/login-passport.js

@@ -199,6 +199,12 @@ module.exports = function(crowi, app) {
    * @param {*} next
    */
   const loginWithLocal = (req, res, next) => {
+    if (!passportService.isLocalStrategySetup) {
+      debug('LocalStrategy has not been set up');
+      req.flash('warningMessage', 'LocalStrategy has not been set up');
+      return next();
+    }
+
     if (!req.form.isValid) {
       return res.render('login', {
       });

+ 12 - 0
src/server/service/config-loader.js

@@ -142,6 +142,18 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
     type:    TYPES.STRING,
     default: undefined,
   },
+  LOCAL_STRATEGY_ENABLED: {
+    ns:      'crowi',
+    key:     'security:passport-local:isEnabled',
+    type:    TYPES.BOOLEAN,
+    default: true,
+  },
+  LOCAL_STRATEGY_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS: {
+    ns:      'crowi',
+    key:     'security:passport-local:useOnlyEnvVarsForSomeOptions',
+    type:    TYPES.BOOLEAN,
+    default: false,
+  },
   SAML_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS: {
     ns:      'crowi',
     key:     'security:passport-saml:useOnlyEnvVarsForSomeOptions',

+ 25 - 19
src/server/service/config-manager.js

@@ -1,6 +1,10 @@
 const logger = require('@alias/logger')('growi:service:ConfigManager');
 const ConfigLoader = require('../service/config-loader');
 
+const KEYS_FOR_LOCAL_STRATEGY_USE_ONLY_ENV_OPTION = [
+  'security:passport-local:isEnabled',
+];
+
 const KEYS_FOR_SAML_USE_ONLY_ENV_OPTION = [
   'security:passport-saml:isEnabled',
   'security:passport-saml:entryPoint',
@@ -50,11 +54,11 @@ class ConfigManager {
   getConfig(namespace, key) {
     let value;
 
-    if (this.searchOnlyFromEnvVarConfigs('crowi', 'security:passport-saml:useOnlyEnvVarsForSomeOptions')) {
-      value = this.searchInSAMLUseOnlyEnvMode(namespace, key);
+    if (this.shouldSearchedFromEnvVarsOnly(namespace, key)) {
+      value = this.searchOnlyFromEnvVarConfigs(namespace, key);
     }
     else {
-    value = this.defaultSearch(namespace, key);
+      value = this.defaultSearch(namespace, key);
     }
 
     logger.debug(key, value);
@@ -175,6 +179,24 @@ class ConfigManager {
     this.reloadConfigKeys();
   }
 
+  /**
+   * return whether the specified namespace/key should be retrieved only from env vars
+   */
+  shouldSearchedFromEnvVarsOnly(namespace, key) {
+    return (namespace === 'crowi' && (
+      // local strategy
+      (
+        KEYS_FOR_LOCAL_STRATEGY_USE_ONLY_ENV_OPTION.includes(key)
+        && this.defaultSearch('crowi', 'security:passport-local:useOnlyEnvVarsForSomeOptions')
+      )
+      // saml strategy
+      || (
+        KEYS_FOR_SAML_USE_ONLY_ENV_OPTION.includes(key)
+        && this.defaultSearch('crowi', 'security:passport-saml:useOnlyEnvVarsForSomeOptions')
+      )
+    ));
+  }
+
   /*
    * All of the methods below are private APIs.
    */
@@ -216,22 +238,6 @@ class ConfigManager {
     }
   }
 
-  /**
-   * For the configs specified by KEYS_FOR_SAML_USE_ONLY_ENV_OPTION,
-   * this searches only from configs loaded from the environment variables.
-   * For the other configs, this searches as the same way to defaultSearch.
-   */
-  /* eslint-disable no-else-return */
-  searchInSAMLUseOnlyEnvMode(namespace, key) {
-    if (namespace === 'crowi' && KEYS_FOR_SAML_USE_ONLY_ENV_OPTION.includes(key)) {
-      return this.searchOnlyFromEnvVarConfigs(namespace, key);
-    }
-    else {
-      return this.defaultSearch(namespace, key);
-    }
-  }
-  /* eslint-enable no-else-return */
-
   /**
    * search a specified config from configs loaded from the database
    */

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

@@ -105,6 +105,15 @@ class PassportService {
       throw new Error('LocalStrategy has already been set up');
     }
 
+    const { configManager } = this.crowi;
+
+    const isEnabled = configManager.getConfig('crowi', 'security:passport-local:isEnabled');
+
+    // when disabled
+    if (!isEnabled) {
+      return;
+    }
+
     debug('LocalStrategy: setting up..');
 
     const User = this.crowi.model('User');

+ 1 - 10
src/server/util/swigFunctions.js

@@ -69,6 +69,7 @@ module.exports = function(crowi, app, req, locals) {
   locals.aclService = aclService;
   locals.fileUploadService = fileUploadService;
   locals.customizeService = customizeService;
+  locals.passportService = passportService;
   locals.pathUtils = pathUtils;
 
   locals.noCdn = function() {
@@ -96,16 +97,6 @@ module.exports = function(crowi, app, req, locals) {
     return cdnResourcesService.getHighlightJsStyleTag(styleName);
   };
 
-  /**
-   * return true if enabled and strategy has been setup successfully
-   */
-  locals.isLdapSetup = function() {
-    return (
-      configManager.getConfig('crowi', 'security:passport-ldap:isEnabled')
-      && passportService.isLdapStrategySetup
-    );
-  };
-
   /**
    * return true if enabled but strategy has some problem
    */

+ 16 - 30
src/server/views/admin/security.html

@@ -59,27 +59,6 @@
             </div>
           </div>
 
-          <div class="form-group">
-            <label for="settingForm[security:registrationMode]" class="col-xs-3 control-label">{{ t('Register limitation') }}</label>
-            <div class="col-xs-6">
-              <select class="form-control selectpicker" name="settingForm[security:registrationMode]" value="{{ getConfig('crowi', 'security:registrationMode') }}">
-                {% for modeValue, modeLabel in consts.registrationMode %}
-                <option value="{{ t(modeValue) }}" {% if modeValue == getConfig('crowi', 'security:registrationMode') %}selected{% endif %} >{{ t(modeLabel) }}</option>
-                {% endfor %}
-              </select>
-              <p class="help-block small">{{ t('security_setting.Register limitation desc') }}</p>
-            </div>
-          </div>
-
-          <div class="form-group">
-            <label for="settingForm[security:registrationWhiteList]" class="col-xs-3 control-label">{{ t('The whitelist of registration permission E-mail address') }}</label>
-            <div class="col-xs-8">
-              <textarea class="form-control" type="textarea" name="settingForm[security:registrationWhiteList]" placeholder="{{ t('security_setting.example') }}: @growi.org">{{ getConfig('crowi', 'security:registrationWhiteList') | join('&#13') | raw }}</textarea>
-              <p class="help-block small">{{ t("security_setting.restrict_emails") }}{{ t("security_setting.for_instance") }}<code>@growi.org</code>{{ t("security_setting.only_those") }}<br>
-              {{ t("security_setting.insert_single") }}</p>
-            </div>
-          </div>
-
           <div class="form-group">
             {% set configName = 'settingForm[security:list-policy:hideRestrictedByOwner]' %}
             {% set configValue = getConfig('crowi', 'security:list-policy:hideRestrictedByOwner') %}
@@ -170,13 +149,16 @@
         <div class="passport-settings">
           <ul class="nav nav-tabs" role="tablist">
             <li class="active">
-              <a href="#passport-saml" data-toggle="tab" role="tab"><i class="fa fa-key"></i> SAML</a>
+              <a href="#passport-local" data-toggle="tab" role="tab"><i class="fa fa-users"></i> ID/Pass</a>
             </li>
             <li>
-              <a href="#passport-oidc" data-toggle="tab" role="tab"><i class="fa fa-openid"></i> OIDC</a>
+              <a href="#passport-ldap" data-toggle="tab" role="tab"><i class="fa fa-sitemap"></i> LDAP</a>
             </li>
             <li>
-              <a href="#passport-ldap" data-toggle="tab" role="tab"><i class="fa fa-sitemap"></i> LDAP</a>
+              <a href="#passport-saml" data-toggle="tab" role="tab"><i class="fa fa-key"></i> SAML</a>
+            </li>
+            <li>
+              <a href="#passport-oidc" data-toggle="tab" role="tab"><i class="fa fa-openid"></i> OIDC</a>
             </li>
             <li>
               <a href="#passport-basic" data-toggle="tab" role="tab"><i class="fa fa-lock"></i> Basic</a>
@@ -196,7 +178,15 @@
           </ul>
 
           <div class="tab-content p-t-10">
-            <div id="passport-saml" class="tab-pane active" role="tabpanel" >
+            <div id="passport-local" class="tab-pane active" role="tabpanel" >
+              {% include './widget/passport/local.html' %}
+            </div>
+
+            <div id="passport-ldap" class="tab-pane" role="tabpanel" >
+              {% include './widget/passport/ldap.html' with { settingForm: settingForm } %}
+            </div>
+
+            <div id="passport-saml" class="tab-pane" role="tabpanel" >
               {% include './widget/passport/saml.html' %}
             </div>
 
@@ -204,10 +194,6 @@
               {% include './widget/passport/oidc.html' %}
             </div>
 
-            <div id="passport-ldap" class="tab-pane" role="tabpanel" >
-              {% include './widget/passport/ldap.html' with { settingForm: settingForm } %}
-            </div>
-
             <div id="passport-basic" class="tab-pane" role="tabpanel">
               {% include './widget/passport/basic.html' %}
             </div>
@@ -236,7 +222,7 @@
   </div>
 
   <script>
-    $('#generalSetting, #samlSetting, #basicSetting, #googleSetting, #githubSetting, #twitterSetting, #oidcSetting').each(function() {
+    $('#generalSetting, #localSetting, #samlSetting, #basicSetting, #googleSetting, #githubSetting, #twitterSetting, #oidcSetting').each(function() {
       $(this).submit(function()
       {
         function showMessage(formId, msg, status) {

+ 84 - 0
src/server/views/admin/widget/passport/local.html

@@ -0,0 +1,84 @@
+<form action="/_api/admin/security/passport-local" method="post" class="form-horizontal passportStrategy" id="localSetting" role="form"
+    {% if isRestartingServerNeeded %}style="opacity: 0.4;"{% endif %}>
+  <legend class="alert-anchor">{{ t("security_setting.Local.name") }} {{ t("security_setting.configuration") }}</legend>
+
+  {% set nameForIsLocalEnabled = "settingForm[security:passport-local:isEnabled]" %}
+  {% set isLocalEnabled = getConfig('crowi', 'security:passport-local:isEnabled') %}
+  {% set useOnlyEnvVars = getConfig('crowi', 'security:passport-local:useOnlyEnvVarsForSomeOptions') %}
+
+  {% if useOnlyEnvVars %}
+    <p class="alert alert-info">
+      {{ t("security_setting.Local.note for the only env option", "LOCAL_STRATEGY_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS") }}
+    </p>
+  {% endif %}
+
+  <div class="form-group">
+    <label for="{{nameForIsLocalEnabled}}" class="col-xs-3 control-label">{{ t("security_setting.Local.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 isLocalEnabled %}active{% endif %}" data-active-class="primary">
+          <input name="{{nameForIsLocalEnabled}}"
+                 value="true"
+                 type="radio"
+                 {% if true === isLocalEnabled %}checked{% endif %}
+                 {% if useOnlyEnvVars %}readonly{% endif %}> ON
+        </label>
+        <label class="btn btn-default btn-rounded btn-outline {% if !isLocalEnabled %}active{% endif %}" data-active-class="default">
+          <input name="{{nameForIsLocalEnabled}}"
+                 value="false"
+                 type="radio"
+                 {% if !isLocalEnabled %}checked{% endif %}
+                 {% if useOnlyEnvVars %}readonly{% endif %}> OFF
+        </label>
+      </div>
+    </div>
+  </div>
+
+
+  <fieldset id="passport-local-hide-when-disabled" {%if !isLocalEnabled %}style="display: none;"{% endif %}>
+
+    <div class="form-group">
+      <label for="settingForm[security:registrationMode]" class="col-xs-3 control-label">{{ t('Register limitation') }}</label>
+      <div class="col-xs-9 col-lg-6">
+        <select class="form-control selectpicker" name="settingForm[security:registrationMode]" value="{{ getConfig('crowi', 'security:registrationMode') }}">
+          {% for modeValue, modeLabel in consts.registrationMode %}
+          <option value="{{ t(modeValue) }}" {% if modeValue == getConfig('crowi', 'security:registrationMode') %}selected{% endif %} >{{ t(modeLabel) }}</option>
+          {% endfor %}
+        </select>
+        <p class="help-block small">{{ t('security_setting.Register limitation desc') }}</p>
+      </div>
+    </div>
+
+    <div class="form-group">
+      <label for="settingForm[security:registrationWhiteList]" class="col-xs-3 control-label">{{ t('The whitelist of registration permission E-mail address') }}</label>
+      <div class="col-xs-9 col-lg-6">
+        <textarea class="form-control" type="textarea" name="settingForm[security:registrationWhiteList]" placeholder="{{ t('security_setting.example') }}: @growi.org">{{ getConfig('crowi', 'security:registrationWhiteList') | join('&#13') | raw }}</textarea>
+        <p class="help-block small">{{ t("security_setting.restrict_emails") }}{{ t("security_setting.for_instance") }}<code>@growi.org</code>{{ t("security_setting.only_those") }}<br>
+        {{ t("security_setting.insert_single") }}</p>
+      </div>
+    </div>
+
+  </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>
+  $('input[name="settingForm[security:passport-local:isEnabled]"]').change(function() {
+    const isEnabled = ($(this).val() === "true");
+
+    if (isEnabled) {
+      $('#passport-local-hide-when-disabled').show(400);
+    }
+    else {
+      $('#passport-local-hide-when-disabled').hide(400);
+    }
+  });
+</script>
+

+ 18 - 6
src/server/views/login.html

@@ -100,14 +100,21 @@
       </div>
     </div>
 
+
+    {% set isLocalOrLdapStrategiesEnabled = passportService.isLocalStrategySetup || passportService.isLdapStrategySetup %}
+    {% set isExternalAuthCollapsible = isLocalOrLdapStrategiesEnabled %}
+    {% set isRegistrationEnabled = passportService.isLocalStrategySetup && getConfig('crowi', 'security:registrationMode') != 'Closed' %}
+
     <div class="login-dialog p-b-10 col-sm-offset-4 col-sm-4 flipper {% if req.query.register or req.body.registerForm or isRegistering %}to-flip{% endif %}" id="login-dialog">
 
       <div class="front">
+
+        {% if isLocalOrLdapStrategiesEnabled %}
         <form role="form" action="/login" method="post">
           <div class="input-group">
             <span class="input-group-addon"><i class="icon-user"></i></span>
             <input type="text" class="form-control" placeholder="Username or E-mail" name="loginForm[username]">
-            {% if isLdapSetup() %}
+            {% if passportService.isLdapStrategySetup %}
             <span class="input-group-addon">
               <small class="text-success">
                 <i class="icon-fw icon-check"></i> LDAP
@@ -129,6 +136,7 @@
             </button>
           </div>
         </form>
+        {% endif %}
 
         {% if (
           getConfig('crowi', 'security:passport-google:isEnabled') ||
@@ -140,7 +148,7 @@
           getConfig('crowi', 'security:passport-basic:isEnabled')
         ) %}
         <hr class="mb-1">
-        <div id="external-auth" class="collapse collapse-external-auth collapse-anchor">
+        <div id="external-auth" class="external-auth {% if isExternalAuthCollapsible %}collapse collapse-external-auth collapse-anchor{% endif %}">
           <div class="spacer"></div>
           <div class="d-flex flex-row justify-content-between flex-wrap">
             {% if getConfig('crowi', 'security:passport-google:isEnabled') %}
@@ -218,7 +226,7 @@
         <hr class="mt-2 mb-0">
         <div class="text-center">
           <button class="collapse-anchor btn btn-xs btn-collapse-external-auth mb-3"
-              data-toggle="collapse" data-target="#external-auth" aria-expanded="false" aria-controls="external-auth">
+              data-toggle="{% if isExternalAuthCollapsible %}collapse{% endif %}" data-target="#external-auth" aria-expanded="false" aria-controls="external-auth">
             External Auth
           </button>
         </div>
@@ -226,6 +234,7 @@
         <hr>
         {% endif %}
 
+        {% if isExternalAuthCollapsible %}
         <script>
           const isMobile = /iphone|ipad|android/.test(window.navigator.userAgent.toLowerCase());
 
@@ -240,10 +249,11 @@
             );
           }
         </script>
+        {% endif %}
 
         <div class="row">
           <div class="col-xs-12 text-right">
-            {% if getConfig('crowi', 'security:registrationMode') != 'Closed' %}
+            {% if isRegistrationEnabled %}
             <a href="#register" id="register" class="link-switch">
               <i class="ti-check-box"></i> {{ t('Sign up is here') }}
             </a>
@@ -252,10 +262,11 @@
             {% endif %}
           </div>
         </div>
+
       </div>
 
 
-      {% if getConfig('crowi', 'security:registrationMode') != 'Closed' %}
+      {% if isRegistrationEnabled %}
       <div class="back">
         {% if getConfig('crowi', 'security:registrationMode') == 'Restricted' %}
         <p class="alert alert-warning">
@@ -317,8 +328,9 @@
             </a>
           </div>
         </div>
+
       </div>
-      {% endif %} {# if registrationMode == Closed #}
+      {% endif %} {# if isRegistrationEnabled id false #}
 
       <a href="https://growi.org" class="link-growi-org">
         <span class="growi">GROWI</span>.<span class="org">ORG