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

Merge pull request #1392 from weseek/create-passport-ldap-option

Create passport ldap option
Yuki Takei 6 лет назад
Родитель
Сommit
558b1337fe

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

@@ -486,8 +486,8 @@
     },
     },
     "configuration": " Configuration",
     "configuration": " Configuration",
     "optional": "Optional",
     "optional": "Optional",
-    "Treat username matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>%s</code> match",
-    "Treat username matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>%s</code>.",
+    "Treat username matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>username</code> match",
+    "Treat username matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>username</code>.",
     "Treat email matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>%s</code> match",
     "Treat email matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>%s</code> match",
     "Treat email matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>%s</code>.",
     "Treat email matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>%s</code>.",
     "Use env var if empty": "Use env var <code>%s</code> if empty",
     "Use env var if empty": "Use env var <code>%s</code> if empty",

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

@@ -481,8 +481,8 @@
     },
     },
     "configuration": "設定",
     "configuration": "設定",
     "optional": "オプション",
     "optional": "オプション",
-    "Treat username matching as identical": "新規ログイン時、<code>%s</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
-    "Treat username matching as identical_warn": "警告: <code>%s</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
+    "Treat username matching as identical": "新規ログイン時、<code>username</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
+    "Treat username matching as identical_warn": "警告: <code>username</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
     "Treat email matching as identical": "新規ログイン時、<code>%s</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
     "Treat email matching as identical": "新規ログイン時、<code>%s</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
     "Treat email matching as identical_warn": "警告: <code>%s</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
     "Treat email matching as identical_warn": "警告: <code>%s</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
     "Use env var if empty": "空の場合、環境変数 <code>%s</code> を利用します",
     "Use env var if empty": "空の場合、環境変数 <code>%s</code> を利用します",

+ 6 - 3
src/client/js/app.jsx

@@ -55,7 +55,8 @@ import EditorContainer from './services/EditorContainer';
 import TagContainer from './services/TagContainer';
 import TagContainer from './services/TagContainer';
 import UserGroupDetailContainer from './services/UserGroupDetailContainer';
 import UserGroupDetailContainer from './services/UserGroupDetailContainer';
 import AdminUsersContainer from './services/AdminUsersContainer';
 import AdminUsersContainer from './services/AdminUsersContainer';
-import AdminSecurityContainer from './services/AdminSecurityContainer';
+import AdminGeneralSecurityContainer from './services/AdminGeneralSecurityContainer';
+import AdminLdapSecurityContainer from './services/AdminLdapSecurityContainer';
 import WebsocketContainer from './services/WebsocketContainer';
 import WebsocketContainer from './services/WebsocketContainer';
 import MarkDownSettingContainer from './services/MarkDownSettingContainer';
 import MarkDownSettingContainer from './services/MarkDownSettingContainer';
 import AdminExternalAccountsContainer from './services/AdminExternalAccountsContainer';
 import AdminExternalAccountsContainer from './services/AdminExternalAccountsContainer';
@@ -214,9 +215,11 @@ if (adminMarkDownSettingElem != null) {
 
 
 const adminSecuritySettingElem = document.getElementById('admin-security-setting');
 const adminSecuritySettingElem = document.getElementById('admin-security-setting');
 if (adminSecuritySettingElem != null) {
 if (adminSecuritySettingElem != null) {
-  const adminSecurityContainer = new AdminSecurityContainer(appContainer);
+  const adminGeneralSecurityContainer = new AdminGeneralSecurityContainer(appContainer);
+  const adminLdapSecurityContainer = new AdminLdapSecurityContainer(appContainer);
+  const adminSecurityContainers = [adminGeneralSecurityContainer, adminLdapSecurityContainer];
   ReactDOM.render(
   ReactDOM.render(
-    <Provider inject={[injectableContainers, adminSecurityContainer]}>
+    <Provider inject={[injectableContainers, adminSecurityContainers]}>
       <I18nextProvider i18n={i18n}>
       <I18nextProvider i18n={i18n}>
         <SecurityManagement />
         <SecurityManagement />
       </I18nextProvider>
       </I18nextProvider>

+ 351 - 0
src/client/js/components/Admin/Security/LdapSecuritySetting.jsx

@@ -0,0 +1,351 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import { createSubscribedElement } from '../../UnstatedUtils';
+
+import AppContainer from '../../../services/AppContainer';
+import AdminGeneralSecurityContainer from '../../../services/AdminGeneralSecurityContainer';
+import AdminLdapSecurityContainer from '../../../services/AdminLdapSecurityContainer';
+
+class LdapSecuritySetting extends React.Component {
+
+  render() {
+    const { t, adminGeneralSecurityContainer, adminLdapSecurityContainer } = this.props;
+    const { isLdapEnabled } = adminGeneralSecurityContainer.state;
+
+    return (
+      <React.Fragment>
+
+        <h2 className="alert-anchor border-bottom">
+          LDAP { t('security_setting.configuration') }
+        </h2>
+
+        <div className="row mb-5">
+          <strong className="col-xs-3 text-right">Use LDAP</strong>
+          <div className="col-xs-6 text-left">
+            <div className="checkbox checkbox-success">
+              <input
+                id="isLdapEnabled"
+                type="checkbox"
+                checked={isLdapEnabled}
+                onChange={() => { adminGeneralSecurityContainer.switchIsLdapEnabled() }}
+              />
+              <label htmlFor="isLdapEnabled">
+                { t('security_setting.ldap.enable_ldap') }
+              </label>
+            </div>
+          </div>
+        </div>
+
+
+        {isLdapEnabled && (
+          <React.Fragment>
+            <div className="row mb-5">
+              <label htmlFor="serverUrl" className="col-xs-3 control-label text-right">Server URL</label>
+              <div className="col-xs-6">
+                <input
+                  className="form-control"
+                  type="text"
+                  name="serverUrl"
+                  value={adminLdapSecurityContainer.state.serverUrl}
+                  onChange={e => adminLdapSecurityContainer.changeServerUrl(e.target.value)}
+                />
+                <small>
+                  <p
+                    className="help-block"
+                    // eslint-disable-next-line react/no-danger
+                    dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.server_url_detail') }}
+                  />
+                  { t('security_setting.example') }: <code>ldaps://ldap.company.com/ou=people,dc=company,dc=com</code>
+                </small>
+              </div>
+            </div>
+
+            <div className="row mb-5">
+              <strong className="col-xs-3 text-right">{ t('security_setting.ldap.bind_mode') }</strong>
+              <div className="col-xs-6 text-left">
+                <div className="my-0 btn-group">
+                  <div className="dropdown">
+                    <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                      <span className="pull-left">{t(`security_setting.ldap.bind_${adminLdapSecurityContainer.state.bindMode}`)}</span>
+                      <span className="bs-caret pull-right">
+                        <span className="caret" />
+                      </span>
+                    </button>
+                    {/* TODO adjust dropdown after BS4 */}
+                    <ul className="dropdown-menu" role="menu">
+                      <li key="manager" role="presentation" type="button" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode('manager') }}>
+                        <a role="menuitem">{ t('security_setting.ldap.bind_manager') }</a>
+                      </li>
+                      <li key="user" role="presentation" type="button" onClick={() => { adminLdapSecurityContainer.changeLdapBindMode('user') }}>
+                        <a role="menuitem">{ t('security_setting.ldap.bind_user') }</a>
+                      </li>
+                    </ul>
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <div className="row mb-5">
+              <strong className="col-xs-3 text-right">Bind DN</strong>
+              <div className="col-xs-6">
+                <input
+                  className="form-control"
+                  type="text"
+                  name="bindDN"
+                  value={adminLdapSecurityContainer.state.bindDN}
+                  onChange={e => adminLdapSecurityContainer.changeBindDN(e.target.value)}
+                />
+                {(adminLdapSecurityContainer.state.bindMode === 'manager') ? (
+                  <p className="help-block passport-ldap-managerbind">
+                    <small>
+                      { t('security_setting.ldap.bind_DN_manager_detail') }<br />
+                      { t('security_setting.example') }1: <code>uid=admin,dc=domain,dc=com</code><br />
+                      { t('security_setting.example') }2: <code>admin@domain.com</code>
+                    </small>
+                  </p>
+                ) : (
+                  <p className="help-block passport-ldap-userbind">
+                    <small>
+                      { t('security_setting.ldap.bind_DN_user_detail1')}<br />
+                      {/* eslint-disable-next-line react/no-danger */}
+                      <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.bind_DN_user_detail2') }} /><br />
+                      { t('security_setting.example') }1: <code>uid={'{{ username }}'},dc=domain,dc=com</code><br />
+                      { t('security_setting.example') }2: <code>{'{{ username }}'}@domain.com</code>
+                    </small>
+                  </p>
+                )}
+              </div>
+            </div>
+
+            <div className="row mb-5">
+              <label htmlFor="bindDNPassword" className="col-xs-3 text-right">{ t('security_setting.ldap.bind_DN_password') }</label>
+              <div className="col-xs-6">
+                <input
+                  className="form-control passport-ldap-managerbind"
+                  type="password"
+                  name="bindDNPassword"
+                  value={adminLdapSecurityContainer.state.bindDNPassword}
+                  onChange={e => adminLdapSecurityContainer.changeBindDNPassword(e.target.value)}
+                />
+                {(adminLdapSecurityContainer.state.bindMode === 'manager') ? (
+                  <p className="help-block passport-ldap-managerbind">
+                    <small>
+                      { t('security_setting.ldap.bind_DN_password_manager_detail') }
+                    </small>
+                  </p>
+                ) : (
+                  <p className="help-block passport-ldap-userbind">
+                    <small>
+                      { t('security_setting.ldap.bind_DN_password_user_detail') }
+                    </small>
+                  </p>
+                )}
+              </div>
+            </div>
+
+            <div className="row mb-5">
+              <strong className="col-xs-3 text-right">{ t('security_setting.ldap.search_filter') }</strong>
+              <div className="col-xs-6">
+                <input
+                  className="form-control"
+                  type="text"
+                  name="searchFilter"
+                  value={adminLdapSecurityContainer.state.searchFilter}
+                  onChange={e => adminLdapSecurityContainer.changeSearchFilter(e.target.value)}
+                />
+                <p className="help-block">
+                  <small>
+                    { t('security_setting.ldap.search_filter_detail1') }<br />
+                    {/* eslint-disable-next-line react/no-danger */}
+                    <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.search_filter_detail2') }} /><br />
+                    {/* eslint-disable-next-line react/no-danger */}
+                    <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.search_filter_detail3') }} />
+                  </small>
+                </p>
+                <p className="help-block">
+                  <small>
+                    { t('security_setting.example') }1 - { t('security_setting.ldap.search_filter_example1') }:
+                    <code>(|(uid={'{{ username }}'})(mail={'{{ username }}'}))</code><br />
+                    { t('security_setting.example') }2 - { t('security_setting.ldap.search_filter_example2') }:
+                    <code>(sAMAccountName={'{{ username }}'})</code>
+                  </small>
+                </p>
+              </div>
+            </div>
+
+            <h3 className="alert-anchor border-bottom">
+              Attribute Mapping ({ t('security_setting.optional') })
+            </h3>
+
+            <div className="row mb-5">
+              <strong htmlFor="attrMapUsername" className="col-xs-3 text-right">{t('username')}</strong>
+              <div className="col-xs-6">
+                <input
+                  className="form-control"
+                  type="text"
+                  placeholder="Default: uid"
+                  name="attrMapUsername"
+                  value={adminLdapSecurityContainer.state.attrMapUsername}
+                  onChange={e => adminLdapSecurityContainer.changeAttrMapUsername(e.target.value)}
+                />
+                <p className="help-block">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <small dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.username_detail') }} />
+                </p>
+              </div>
+            </div>
+
+            <div className="row mb-5">
+              <div className="col-xs-offset-3 col-xs-6 text-left">
+                <div className="checkbox checkbox-success">
+                  <input
+                    id="cbSameUsernameTreatedAsIdenticalUser"
+                    type="checkbox"
+                    checked={adminLdapSecurityContainer.state.cbSameUsernameTreatedAsIdenticalUser}
+                    onChange={() => { adminLdapSecurityContainer.switchCbSameUsernameTreatedAsIdenticalUser() }}
+                  />
+                  <label
+                    htmlFor="cbSameUsernameTreatedAsIdenticalUser"
+                    // eslint-disable-next-line react/no-danger
+                    dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical') }}
+                  />
+                </div>
+                <p className="help-block">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <small dangerouslySetInnerHTML={{ __html: t('security_setting.Treat username matching as identical_warn') }} />
+                </p>
+              </div>
+            </div>
+
+            <div className="row mb-5">
+              <strong htmlFor="attrMapMail" className="col-xs-3 text-right">{ t('Email') }</strong>
+              <div className="col-xs-6">
+                <input
+                  className="form-control"
+                  type="text"
+                  placeholder="Default: mail"
+                  name="attrMapMail"
+                  value={adminLdapSecurityContainer.state.attrMapMail}
+                  onChange={e => adminLdapSecurityContainer.changeAttrMapMail(e.target.value)}
+                />
+                <p className="help-block">
+                  <small>
+                    { t('security_setting.ldap.mail_detail') }
+                  </small>
+                </p>
+              </div>
+            </div>
+
+            <div className="row mb-5">
+              <strong htmlFor="attrMapName" className="col-xs-3 text-right">{ t('Name') }</strong>
+              <div className="col-xs-6">
+                <input
+                  className="form-control"
+                  type="text"
+                  name="attrMapName"
+                  value={adminLdapSecurityContainer.state.attrMapName}
+                  onChange={e => adminLdapSecurityContainer.changeAttrMapName(e.target.value)}
+                />
+                <p className="help-block">
+                  <small>
+                    { t('security_setting.ldap.name_detail') }
+                  </small>
+                </p>
+              </div>
+            </div>
+
+
+            <h3 className="alert-anchor border-bottom">
+              { t('security_setting.ldap.group_search_filter') } ({ t('security_setting.optional') })
+            </h3>
+
+            <div className="row mb-5">
+              <strong htmlFor="groupSearchBase" className="col-xs-3 text-right">{ t('security_setting.ldap.group_search_base_DN') }</strong>
+              <div className="col-xs-6">
+                <input
+                  className="form-control"
+                  type="text"
+                  name="groupSearchBase"
+                  value={adminLdapSecurityContainer.state.groupSearchBase}
+                  onChange={e => adminLdapSecurityContainer.changeGroupSearchBase(e.target.value)}
+                />
+                <p className="help-block">
+                  <small>
+                    {/* eslint-disable-next-line react/no-danger */}
+                    <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_base_DN_detail') }} /><br />
+                    { t('security_setting.example') }: <code>ou=groups,dc=domain,dc=com</code>
+                  </small>
+                </p>
+              </div>
+            </div>
+
+            <div className="row mb-5">
+              <strong htmlFor="groupSearchFilter" className="col-xs-3 text-right">{ t('security_setting.ldap.group_search_filter') }</strong>
+              <div className="col-xs-6">
+                <input
+                  className="form-control"
+                  type="text"
+                  name="groupSearchFilter"
+                  value={adminLdapSecurityContainer.state.groupSearchFilter}
+                  onChange={e => adminLdapSecurityContainer.changeGroupSearchFilter(e.target.value)}
+                />
+                <p className="help-block">
+                  <small>
+                    {/* eslint-disable react/no-danger */}
+                    <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_filter_detail1') }} /><br />
+                    <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_filter_detail2') }} /><br />
+                    <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_filter_detail3') }} />
+                    {/* eslint-enable react/no-danger */}
+                  </small>
+                </p>
+                <p className="help-block">
+                  <small>
+                    { t('security_setting.example') }:
+                    {/* eslint-disable-next-line react/no-danger */}
+                    <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_filter_detail4') }} />
+                  </small>
+                </p>
+              </div>
+            </div>
+
+            <div className="row mb-5">
+              <label htmlFor="groupDnProperty" className="col-xs-3 text-right">{ t('security_setting.ldap.group_search_user_DN_property') }</label>
+              <div className="col-xs-6">
+                <input
+                  className="form-control"
+                  type="text"
+                  placeholder="Default: uid"
+                  name="groupDnProperty"
+                  value={adminLdapSecurityContainer.state.groupDnProperty}
+                  onChange={e => adminLdapSecurityContainer.changeGroupDnProperty(e.target.value)}
+                />
+                <p className="help-block">
+                  {/* eslint-disable-next-line react/no-danger */}
+                  <small dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.group_search_user_DN_property_detail') }} />
+                </p>
+              </div>
+            </div>
+
+          </React.Fragment>
+        )}
+
+      </React.Fragment>
+    );
+  }
+
+}
+
+LdapSecuritySetting.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminGeneralSecurityContainer: PropTypes.instanceOf(AdminGeneralSecurityContainer).isRequired,
+  adminLdapSecurityContainer: PropTypes.instanceOf(AdminLdapSecurityContainer).isRequired,
+};
+
+const LdapSecuritySettingWrapper = (props) => {
+  return createSubscribedElement(LdapSecuritySetting, props, [AppContainer, AdminGeneralSecurityContainer, AdminLdapSecurityContainer]);
+};
+
+export default withTranslation()(LdapSecuritySettingWrapper);

+ 32 - 17
src/client/js/components/Admin/Security/SecurityLocalSetting.jsx → src/client/js/components/Admin/Security/LocalSecuritySetting.jsx

@@ -5,12 +5,12 @@ import { withTranslation } from 'react-i18next';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { createSubscribedElement } from '../../UnstatedUtils';
 
 
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
-import AdminSecurityContainer from '../../../services/AdminSecurityContainer';
+import AdminGeneralSecurityContainer from '../../../services/AdminGeneralSecurityContainer';
 
 
-class SecurityLocalSetting extends React.Component {
+class LocalSecuritySetting extends React.Component {
 
 
   render() {
   render() {
-    const { t, adminSecurityContainer } = this.props;
+    const { t, adminGeneralSecurityContainer } = this.props;
 
 
     return (
     return (
       <React.Fragment>
       <React.Fragment>
@@ -19,7 +19,7 @@ class SecurityLocalSetting extends React.Component {
           { t('security_setting.Local.name') } { t('security_setting.configuration') }
           { t('security_setting.Local.name') } { t('security_setting.configuration') }
         </h2>
         </h2>
 
 
-        {adminSecurityContainer.state.useOnlyEnvVarsForSomeOptions && (
+        {adminGeneralSecurityContainer.state.useOnlyEnvVarsForSomeOptions && (
         <p className="alert alert-info">
         <p className="alert alert-info">
           { t('security_setting.Local.note for the only env option', 'LOCAL_STRATEGY_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS') }
           { t('security_setting.Local.note for the only env option', 'LOCAL_STRATEGY_USES_ONLY_ENV_VARS_FOR_SOME_OPTIONS') }
         </p>
         </p>
@@ -32,8 +32,8 @@ class SecurityLocalSetting extends React.Component {
               <input
               <input
                 id="isLocalEnabled"
                 id="isLocalEnabled"
                 type="checkbox"
                 type="checkbox"
-                checked={adminSecurityContainer.state.isLocalEnabled}
-                onChange={() => { adminSecurityContainer.switchIsLocalEnabled() }}
+                checked={adminGeneralSecurityContainer.state.isLocalEnabled}
+                onChange={() => { adminGeneralSecurityContainer.switchIsLocalEnabled() }}
               />
               />
               <label htmlFor="isLocalEnabled">
               <label htmlFor="isLocalEnabled">
                 { t('security_setting.Local.enable_local') }
                 { t('security_setting.Local.enable_local') }
@@ -42,7 +42,7 @@ class SecurityLocalSetting extends React.Component {
           </div>
           </div>
         </div>
         </div>
 
 
-        {adminSecurityContainer.state.isLocalEnabled && (
+        {adminGeneralSecurityContainer.state.isLocalEnabled && (
           <div>
           <div>
             <div className="row mb-5">
             <div className="row mb-5">
               <strong className="col-xs-3 text-right">{ t('Register limitation') }</strong>
               <strong className="col-xs-3 text-right">{ t('Register limitation') }</strong>
@@ -50,20 +50,35 @@ class SecurityLocalSetting extends React.Component {
                 <div className="my-0 btn-group">
                 <div className="my-0 btn-group">
                   <div className="dropdown">
                   <div className="dropdown">
                     <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                     <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                      <span className="pull-left">{t(`security_setting.registration_mode.${adminSecurityContainer.state.registrationMode}`)}</span>
+                      <span className="pull-left">{t(`security_setting.registration_mode.${adminGeneralSecurityContainer.state.registrationMode}`)}</span>
                       <span className="bs-caret pull-right">
                       <span className="bs-caret pull-right">
                         <span className="caret" />
                         <span className="caret" />
                       </span>
                       </span>
                     </button>
                     </button>
                     {/* TODO adjust dropdown after BS4 */}
                     {/* TODO adjust dropdown after BS4 */}
                     <ul className="dropdown-menu" role="menu">
                     <ul className="dropdown-menu" role="menu">
-                      <li key="open" role="presentation" type="button" onClick={() => { adminSecurityContainer.changeRegistrationMode('open') }}>
+                      <li
+                        key="open"
+                        role="presentation"
+                        type="button"
+                        onClick={() => { adminGeneralSecurityContainer.changeRegistrationMode('open') }}
+                      >
                         <a role="menuitem">{ t('security_setting.registration_mode.open') }</a>
                         <a role="menuitem">{ t('security_setting.registration_mode.open') }</a>
                       </li>
                       </li>
-                      <li key="restricted" role="presentation" type="button" onClick={() => { adminSecurityContainer.changeRegistrationMode('restricted') }}>
+                      <li
+                        key="restricted"
+                        role="presentation"
+                        type="button"
+                        onClick={() => { adminGeneralSecurityContainer.changeRegistrationMode('restricted') }}
+                      >
                         <a role="menuitem">{ t('security_setting.registration_mode.restricted') }</a>
                         <a role="menuitem">{ t('security_setting.registration_mode.restricted') }</a>
                       </li>
                       </li>
-                      <li key="closed" role="presentation" type="button" onClick={() => { adminSecurityContainer.changeRegistrationMode('closed') }}>
+                      <li
+                        key="closed"
+                        role="presentation"
+                        type="button"
+                        onClick={() => { adminGeneralSecurityContainer.changeRegistrationMode('closed') }}
+                      >
                         <a role="menuitem">{ t('security_setting.registration_mode.closed') }</a>
                         <a role="menuitem">{ t('security_setting.registration_mode.closed') }</a>
                       </li>
                       </li>
                     </ul>
                     </ul>
@@ -83,7 +98,7 @@ class SecurityLocalSetting extends React.Component {
                     className="form-control"
                     className="form-control"
                     type="textarea"
                     type="textarea"
                     name="registrationWhiteList"
                     name="registrationWhiteList"
-                    placeholder={adminSecurityContainer.state.registrationWhiteList}
+                    placeholder={adminGeneralSecurityContainer.state.registrationWhiteList}
                   />
                   />
                   <p className="help-block small">{ t('security_setting.restrict_emails') }<br />{ t('security_setting.for_instance') }
                   <p className="help-block small">{ t('security_setting.restrict_emails') }<br />{ t('security_setting.for_instance') }
                     <code>@growi.org</code>{ t('security_setting.only_those') }<br />
                     <code>@growi.org</code>{ t('security_setting.only_those') }<br />
@@ -106,14 +121,14 @@ class SecurityLocalSetting extends React.Component {
 
 
 }
 }
 
 
-SecurityLocalSetting.propTypes = {
+LocalSecuritySetting.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  adminSecurityContainer: PropTypes.instanceOf(AdminSecurityContainer).isRequired,
+  adminGeneralSecurityContainer: PropTypes.instanceOf(AdminGeneralSecurityContainer).isRequired,
 };
 };
 
 
-const SecurityLocalSettingWrapper = (props) => {
-  return createSubscribedElement(SecurityLocalSetting, props, [AppContainer, AdminSecurityContainer]);
+const LocalSecuritySettingWrapper = (props) => {
+  return createSubscribedElement(LocalSecuritySetting, props, [AppContainer, AdminGeneralSecurityContainer]);
 };
 };
 
 
-export default withTranslation()(SecurityLocalSettingWrapper);
+export default withTranslation()(LocalSecuritySettingWrapper);

+ 0 - 208
src/client/js/components/Admin/Security/SecurityLdapSetting.jsx

@@ -1,208 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { withTranslation } from 'react-i18next';
-
-import { createSubscribedElement } from '../../UnstatedUtils';
-
-import AppContainer from '../../../services/AppContainer';
-import AdminSecurityContainer from '../../../services/AdminSecurityContainer';
-
-class SecurityLdapSetting extends React.Component {
-
-  render() {
-    const { t, adminSecurityContainer } = this.props;
-    const { ldapConfig } = adminSecurityContainer.state;
-
-    return (
-      <React.Fragment>
-
-        <h2 className="alert-anchor border-bottom">
-          LDAP { t('security_setting.configuration') }
-        </h2>
-
-        <div className="row mb-5">
-          <strong className="col-xs-3 text-right">Use LDAP</strong>
-          <div className="col-xs-6 text-left">
-            <div className="checkbox checkbox-success">
-              <input
-                id="isLdapEnabled"
-                type="checkbox"
-                checked={ldapConfig.isEnabled}
-                onChange={() => { adminSecurityContainer.switchIsLdapEnabled() }}
-              />
-              <label htmlFor="isLdapEnabled">
-                { t('security_setting.ldap.enable_ldap') }
-              </label>
-            </div>
-          </div>
-        </div>
-
-
-        {ldapConfig.isEnabled && (
-          <React.Fragment>
-            <div className="row mb-5">
-              <label htmlFor="serverUrl" className="col-xs-3 control-label text-right">Server URL</label>
-              <div className="col-xs-6">
-                <input
-                  className="form-control"
-                  type="text"
-                  name="serverUrl"
-                  value={ldapConfig.serverUrl}
-                  onChange={e => adminSecurityContainer.changeServerUrl(e.target.value)}
-                />
-                <small>
-                  <p
-                    className="help-block"
-                    // eslint-disable-next-line react/no-danger
-                    dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.server_url_detail') }}
-                  />
-                  { t('security_setting.example') }: <code>ldaps://ldap.company.com/ou=people,dc=company,dc=com</code>
-                </small>
-              </div>
-            </div>
-
-            <div className="row mb-5">
-              <strong className="col-xs-3 text-right">{ t('security_setting.ldap.bind_mode') }</strong>
-              <div className="col-xs-6 text-left">
-                <div className="my-0 btn-group">
-                  <div className="dropdown">
-                    <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                      <span className="pull-left">{t(`security_setting.ldap.bind_${ldapConfig.bindMode}`)}</span>
-                      <span className="bs-caret pull-right">
-                        <span className="caret" />
-                      </span>
-                    </button>
-                    {/* TODO adjust dropdown after BS4 */}
-                    <ul className="dropdown-menu" role="menu">
-                      <li key="manager" role="presentation" type="button" onClick={() => { adminSecurityContainer.changeLdapBindMode('manager') }}>
-                        <a role="menuitem">{ t('security_setting.ldap.bind_manager') }</a>
-                      </li>
-                      <li key="user" role="presentation" type="button" onClick={() => { adminSecurityContainer.changeLdapBindMode('user') }}>
-                        <a role="menuitem">{ t('security_setting.ldap.bind_user') }</a>
-                      </li>
-                    </ul>
-                  </div>
-                </div>
-              </div>
-            </div>
-
-            <div className="row mb-5">
-              <strong className="col-xs-3 text-right">Bind DN</strong>
-              <div className="col-xs-6">
-                <input
-                  className="form-control"
-                  type="text"
-                  name="bindDN"
-                  value={ldapConfig.bindDN}
-                  onChange={e => adminSecurityContainer.changeBindDN(e.target.value)}
-                />
-                {(ldapConfig.bindMode === 'manager') ? (
-                  <p className="help-block passport-ldap-managerbind">
-                    <small>
-                      { t('security_setting.ldap.bind_DN_manager_detail') }<br />
-                      { t('security_setting.example') }1: <code>uid=admin,dc=domain,dc=com</code><br />
-                      { t('security_setting.example') }2: <code>admin@domain.com</code>
-                    </small>
-                  </p>
-                ) : (
-                  <p className="help-block passport-ldap-userbind">
-                    <small>
-                      { t('security_setting.ldap.bind_DN_user_detail1')}<br />
-                      {/* eslint-disable-next-line react/no-danger */}
-                      <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.bind_DN_user_detail2') }} /><br />
-                      { t('security_setting.example') }1: <code>uid={'{{ username }}'},dc=domain,dc=com</code><br />
-                      { t('security_setting.example') }2: <code>{'{{ username }}'}@domain.com</code>
-                    </small>
-                  </p>
-                )}
-              </div>
-            </div>
-
-            <div className="row mb-5">
-              <label htmlFor="bindDNPassword" className="col-xs-3 control-label text-right">{ t('security_setting.ldap.bind_DN_password') }</label>
-              <div className="col-xs-6">
-                <input
-                  className="form-control passport-ldap-managerbind"
-                  type="password"
-                  name="bindDNPassword"
-                  value={ldapConfig.bindDNPassword}
-                  onChange={e => adminSecurityContainer.changeBindDNPassword(e.target.value)}
-                />
-                {(ldapConfig.bindMode === 'manager') ? (
-                  <p className="help-block passport-ldap-managerbind">
-                    <small>
-                      { t('security_setting.ldap.bind_DN_password_manager_detail') }
-                    </small>
-                  </p>
-                ) : (
-                  <p className="help-block passport-ldap-userbind">
-                    <small>
-                      { t('security_setting.ldap.bind_DN_password_user_detail') }
-                    </small>
-                  </p>
-                )}
-              </div>
-            </div>
-
-            <div className="row mb-5">
-              <strong className="col-xs-3 text-right">{ t('security_setting.ldap.search_filter') }</strong>
-              <div className="col-xs-6">
-                <input
-                  className="form-control"
-                  type="text"
-                  name="searchFilter"
-                  value={ldapConfig.searchFilter}
-                  onChange={e => adminSecurityContainer.changeSearchFilter(e.target.value)}
-                />
-                <p className="help-block">
-                  <small>
-                    { t('security_setting.ldap.search_filter_detail1') }<br />
-                    {/* eslint-disable-next-line react/no-danger */}
-                    <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.search_filter_detail2') }} /><br />
-                    {/* eslint-disable-next-line react/no-danger */}
-                    <span dangerouslySetInnerHTML={{ __html: t('security_setting.ldap.search_filter_detail3') }} />
-                  </small>
-                </p>
-                <p className="help-block">
-                  <small>
-                    { t('security_setting.example') }1 - { t('security_setting.ldap.search_filter_example1') }:
-                    <code>(|(uid={'{{ username }}'})(mail={'{{ username }}'}))</code><br />
-                    { t('security_setting.example') }2 - { t('security_setting.ldap.search_filter_example2') }:
-                    <code>(sAMAccountName={'{{ username }}'})</code>
-                  </small>
-                </p>
-              </div>
-            </div>
-
-            <h3 className="alert-anchor border-bottom">
-              Attribute Mapping ({ t('security_setting.optional') })
-            </h3>
-
-            {/* GW-617 create form */}
-
-            <h3 className="alert-anchor border-bottom">
-              { t('security_setting.ldap.group_search_filter') } ({ t('security_setting.optional') })
-            </h3>
-
-            {/* GW-617 create form */}
-
-          </React.Fragment>
-        )}
-
-      </React.Fragment>
-    );
-  }
-
-}
-
-SecurityLdapSetting.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  adminSecurityContainer: PropTypes.instanceOf(AdminSecurityContainer).isRequired,
-};
-
-const SecurityLdapSettingWrapper = (props) => {
-  return createSubscribedElement(SecurityLdapSetting, props, [AppContainer, AdminSecurityContainer]);
-};
-
-export default withTranslation()(SecurityLdapSettingWrapper);

+ 4 - 4
src/client/js/components/Admin/Security/SecurityManagement.jsx

@@ -5,8 +5,8 @@ import { withTranslation } from 'react-i18next';
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { createSubscribedElement } from '../../UnstatedUtils';
 
 
 import AppContainer from '../../../services/AppContainer';
 import AppContainer from '../../../services/AppContainer';
-import SecurityLocalSetting from './SecurityLocalSetting';
-import SecurityLdapSetting from './SecurityLdapSetting';
+import LdapSecuritySetting from './LdapSecuritySetting';
+import LocalSecuritySetting from './LocalSecuritySetting';
 
 
 class SecurityManagement extends React.Component {
 class SecurityManagement extends React.Component {
 
 
@@ -141,10 +141,10 @@ class SecurityManagement extends React.Component {
             </ul>
             </ul>
             <div className="tab-content p-t-10">
             <div className="tab-content p-t-10">
               <div id="passport-local" className="tab-pane active" role="tabpanel">
               <div id="passport-local" className="tab-pane active" role="tabpanel">
-                <SecurityLocalSetting />
+                <LocalSecuritySetting />
               </div>
               </div>
               <div id="passport-ldap" className="tab-pane" role="tabpanel">
               <div id="passport-ldap" className="tab-pane" role="tabpanel">
-                <SecurityLdapSetting />
+                <LdapSecuritySetting />
               </div>
               </div>
               <div id="passport-saml" className="tab-pane" role="tabpanel">
               <div id="passport-saml" className="tab-pane" role="tabpanel">
                 {/* TODO GW-544 reactify saml.html */}
                 {/* TODO GW-544 reactify saml.html */}

+ 69 - 0
src/client/js/services/AdminGeneralSecurityContainer.js

@@ -0,0 +1,69 @@
+import { Container } from 'unstated';
+
+import loggerFactory from '@alias/logger';
+
+// eslint-disable-next-line no-unused-vars
+const logger = loggerFactory('growi:security:AdminGeneralSecurityContainer');
+
+/**
+ * Service container for admin security page (SecurityManagement.jsx)
+ * @extends {Container} unstated Container
+ */
+export default class AdminGeneralSecurityContainer extends Container {
+
+  constructor(appContainer) {
+    super();
+
+    this.appContainer = appContainer;
+
+    this.state = {
+      // TODO GW-583 set value
+      useOnlyEnvVarsForSomeOptions: true,
+      isLocalEnabled: true,
+      registrationMode: 'open',
+      registrationWhiteList: '',
+      isLdapEnabled: true,
+    };
+
+    this.init();
+
+    this.switchIsLocalEnabled = this.switchIsLocalEnabled.bind(this);
+    this.changeRegistrationMode = this.changeRegistrationMode.bind(this);
+  }
+
+  init() {
+    // TODO GW-583 fetch config value with api
+  }
+
+
+  /**
+   * Workaround for the mangling in production build to break constructor.name
+   */
+  static getClassName() {
+    return 'AdminGeneralSecurityContainer';
+  }
+
+  /**
+   * Switch local enabled
+   */
+  switchIsLocalEnabled() {
+    this.setState({ isLocalEnabled: !this.state.isLocalEnabled });
+  }
+
+  /**
+   * Change registration mode
+   */
+  changeRegistrationMode(value) {
+    this.setState({ registrationMode: value });
+  }
+
+  // LDAP function
+
+  /**
+   * Switch local enabled
+   */
+  switchIsLdapEnabled() {
+    this.setState({ isLdapEnabled: !this.state.isLdapEnabled });
+  }
+
+}

+ 135 - 0
src/client/js/services/AdminLdapSecurityContainer.js

@@ -0,0 +1,135 @@
+import { Container } from 'unstated';
+
+import loggerFactory from '@alias/logger';
+
+// eslint-disable-next-line no-unused-vars
+const logger = loggerFactory('growi:security:AdminLdapSecurityLdapContainer');
+
+/**
+ * Service container for admin security page (SecurityLdapSetting.jsx)
+ * @extends {Container} unstated Container
+ */
+export default class AdminLdapSecurityContainer extends Container {
+
+  constructor(appContainer) {
+    super();
+
+    this.appContainer = appContainer;
+
+    this.state = {
+      // TODO GW-583 set value
+      serverUrl: '',
+      bindMode: 'manager',
+      bindDN: '',
+      bindDNPassword: '',
+      searchFilter: '',
+      attrMapUsername: '',
+      cbSameUsernameTreatedAsIdenticalUser: true,
+      attrMapMail: '',
+      attrMapName: '',
+      groupSearchBase: '',
+      groupSearchFilter: '',
+      groupDnProperty: '',
+    };
+
+    this.init();
+
+  }
+
+  init() {
+    // TODO GW-583 fetch config value with api
+  }
+
+
+  /**
+   * Workaround for the mangling in production build to break constructor.name
+   */
+  static getClassName() {
+    return 'AdminLdapSecurityContainer';
+  }
+
+  /**
+   * Change server url
+   */
+  changeServerUrl(inputValue) {
+    this.setState({ serverUrl: inputValue });
+  }
+
+  /**
+   * Change ldap bind mode
+   */
+  changeLdapBindMode(mode) {
+    this.setState({ bindMode: mode });
+  }
+
+  /**
+   * Change bind DN
+   */
+  changeBindDN(inputValue) {
+    this.setState({ bindDN: inputValue });
+  }
+
+  /**
+   * Change bind DN password
+   */
+  changeBindDNPassword(inputValue) {
+    this.setState({ bindDNPassword: inputValue });
+  }
+
+  /**
+   * Change search filter
+   */
+  changeSearchFilter(inputValue) {
+    this.setState({ searchFilter: inputValue });
+  }
+
+  /**
+   * Change attr map username
+   */
+  changeAttrMapUsername(inputValue) {
+    this.setState({ attrMapUsername: inputValue });
+  }
+
+  /**
+   * Switch cb same username treated as identical user
+   */
+  switchCbSameUsernameTreatedAsIdenticalUser() {
+    this.setState({ cbSameUsernameTreatedAsIdenticalUser: !this.state.cbSameUsernameTreatedAsIdenticalUser });
+  }
+
+  /**
+   * Change attr map email
+   */
+  changeAttrMapMail(inputValue) {
+    this.setState({ attrMapMail: inputValue });
+  }
+
+  /**
+   * Change attr map name
+   */
+  changeAttrMapName(inputValue) {
+    this.setState({ attrMapName: inputValue });
+  }
+
+  /**
+   * Change group search base
+   */
+  changeGroupSearchBase(inputValue) {
+    this.setState({ groupSearchBase: inputValue });
+  }
+
+  /**
+   * Change group search filter
+   */
+  changeGroupSearchFilter(inputValue) {
+    this.setState({ groupSearchFilter: inputValue });
+  }
+
+  /**
+   * Change group dn property
+   */
+  changeGroupDnProperty(inputValue) {
+    this.setState({ groupDnProperty: inputValue });
+  }
+
+}

+ 0 - 124
src/client/js/services/AdminSecurityContainer.js

@@ -1,124 +0,0 @@
-import { Container } from 'unstated';
-
-import loggerFactory from '@alias/logger';
-
-// eslint-disable-next-line no-unused-vars
-const logger = loggerFactory('growi:services:AdminSecurityContainer');
-
-/**
- * Service container for admin security page (SecurityManagement.jsx)
- * @extends {Container} unstated Container
- */
-export default class AdminSecurityContainer extends Container {
-
-  constructor(appContainer) {
-    super();
-
-    this.appContainer = appContainer;
-
-    this.state = {
-      // TODO GW-583 set value
-      useOnlyEnvVarsForSomeOptions: true,
-      isLocalEnabled: true,
-      registrationMode: 'open',
-      registrationWhiteList: '',
-      ldapConfig: {
-        isEnabled: true,
-        serverUrl: '',
-        bindMode: 'manager',
-        bindDN: '',
-        bindDNPassword: '',
-        searchFilter: '',
-      },
-    };
-
-    this.init();
-
-    this.switchIsLocalEnabled = this.switchIsLocalEnabled.bind(this);
-    this.changeRegistrationMode = this.changeRegistrationMode.bind(this);
-  }
-
-  init() {
-    // TODO GW-583 fetch config value with api
-  }
-
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'AdminSecurityContainer';
-  }
-
-  /**
-   * Switch local enabled
-   */
-  switchIsLocalEnabled() {
-    this.setState({ isLocalEnabled: !this.state.isLocalEnabled });
-  }
-
-  /**
-   * Change registration mode
-   */
-  changeRegistrationMode(value) {
-    this.setState({ registrationMode: value });
-  }
-
-  // LDAP function
-
-  /**
-   * Switch local enabled
-   */
-  switchIsLdapEnabled() {
-    const newLdapConfig = this.state.ldapConfig;
-    newLdapConfig.isEnabled = !this.state.ldapConfig.isEnabled;
-    this.setState({ newLdapConfig });
-  }
-
-  /**
-   * Change server url
-   */
-  changeServerUrl(inputValue) {
-    const newLdapConfig = this.state.ldapConfig;
-    newLdapConfig.serverUrl = inputValue;
-    this.setState({ newLdapConfig });
-  }
-
-  /**
-   * Change ldap bind mode
-   */
-  changeLdapBindMode(mode) {
-    const newLdapConfig = this.state.ldapConfig;
-    newLdapConfig.bindMode = mode;
-    this.setState({ newLdapConfig });
-  }
-
-  /**
-   * Change bind DN
-   */
-  changeBindDN(inputValue) {
-    const newLdapConfig = this.state.ldapConfig;
-    newLdapConfig.bindDN = inputValue;
-    this.setState({ newLdapConfig });
-  }
-
-  /**
-   * Change bind DN password
-   */
-  changeBindDNPassword(inputValue) {
-    const newLdapConfig = this.state.ldapConfig;
-    newLdapConfig.bindDNPassword = inputValue;
-    this.setState({ newLdapConfig });
-  }
-
-  /**
-   * Change search filter
-   */
-  changeSearchFilter(inputValue) {
-    const newLdapConfig = this.state.ldapConfig;
-    newLdapConfig.searchFilter = inputValue;
-    this.setState({ newLdapConfig });
-  }
-
-
-}