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

Merge branch 'support/apply-bootstrap4' into support/apply-bootstrap4-installer-form

yusuketk 6 лет назад
Родитель
Сommit
23bf59630d

+ 4 - 0
CHANGES.md

@@ -6,6 +6,10 @@
 * Feature: SAML Attribute-based Login Control
 * Improvement: Reactify admin pages (Security)
 
+## v3.6.10
+
+* Fix: Redirect logic for users except for actives
+    * Introduced by 3.6.9
 
 ## v3.6.9
 

+ 1 - 1
config/env.prod.js

@@ -1,4 +1,4 @@
 module.exports = {
   NODE_ENV: 'production',
-  // FORMAT_NODE_LOG: false,
+  // FORMAT_NODE_LOG: false, // default: true
 };

+ 3 - 0
config/logger/config.prod.js

@@ -1,3 +1,6 @@
 module.exports = {
   default: 'info',
+
+  'growi:routes:login-passport': 'debug',
+  'growi:service:PassportService': 'debug',
 };

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

@@ -422,6 +422,7 @@
     "missing mandatory configs": "The following mandatory items are not set in either database nor environment variables.",
     "Local": {
       "name": "ID/Password",
+      "note for the only env option": "The LOCAL authentication is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}/code> .",
       "enable_local": "enable ID/Password"
     },
     "ldap": {
@@ -465,7 +466,7 @@
       "mapping_detail": "Specification of mappings for {{target}} when creating new users",
       "cert_detail": "PEM-encoded X.509 signing certificate to validate the response from IdP",
       "Use env var if empty": "If the value in the database is empty, the value of the environment variable <code>{{env}}</code> is used.",
-      "note for the only env option": "The setting item that enables or disables the SAML authentication and the highlighted setting items use only the value of environment variables.<br>To change this setting, please change to false or delete the value of the environment variable <code>%s</code> .",
+      "note for the only env option": "The setting item that enables or disables the SAML authentication and the highlighted setting items use only the value of environment variables.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
       "attr_based_login_control_detail": "Limit who can sign up by using <code>&lt;saml: Attribute&gt;</code> element included in <code>&lt;saml: AttributeStatement&gt;</code> element and its child element <code>&lt;saml: AttributeValue&gt;</code>.",
       "attr_based_login_control_rule_detail": "See <a href=\"https://lucene.apache.org/core/2_9_4/queryparsersyntax.html\" target=\"_blank\">Apache Lucene - Query Parser Syntax</a>.<h6>Supported Queries:</h6><ul><li>Terms</li><li>Fields</li><li>AND/NOT/OR Operator</li><li>Grouping</li></ul><h6>Unsupported Queries:</h6><ul><li>Wildcard, Fuzzy, Proximity, Range and Boosting</li><li>+/- Operator</li><li>Field Grouping</li></ul>",
       "attr_based_login_control_rule_example": "<h6>Example</h6>If a rule is <code>(Department: A || Department: B) && Position: Leader</code>, users who have either <code>Department: A</code> or <code>Department: B</code> and have <code>Position: Leader</code> <strong>can</strong> sign in.",

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

@@ -416,6 +416,7 @@
     "missing mandatory configs": "以下の必須項目の値がデータベースと環境変数のどちらにも設定されていません",
     "Local": {
       "name": "ID/Password",
+      "note for the only env option": "現在LOCAL認証のON/OFFは環境変数の値によって制限されています<br>この設定を変更する場合は環境変数 <code>{{env}}</code> の値をfalseに変更もしくは削除してください",
       "enable_local": "ID/Password を有効にする"
     },
     "ldap": {

+ 2 - 1
src/client/js/components/Admin/Security/LocalSecuritySetting.jsx

@@ -66,7 +66,7 @@ class LocalSecuritySetting extends React.Component {
           {t('security_setting.Local.name')}
         </h2>
 
-        {adminGeneralSecurityContainer.state.useOnlyEnvVarsForSomeOptions && (
+        {adminLocalSecurityContainer.state.useOnlyEnvVars && (
           <p
             className="alert alert-info"
             // eslint-disable-next-line max-len
@@ -85,6 +85,7 @@ class LocalSecuritySetting extends React.Component {
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isLocalEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsLocalEnabled() }}
+                disabled={adminLocalSecurityContainer.state.useOnlyEnvVars}
               />
               <label htmlFor="isLocalEnabled">
                 {t('security_setting.Local.enable_local')}

+ 2 - 1
src/client/js/components/Admin/Security/SamlSecuritySetting.jsx

@@ -100,6 +100,7 @@ class SamlSecurityManagement extends React.Component {
                 type="checkbox"
                 checked={adminGeneralSecurityContainer.state.isSamlEnabled}
                 onChange={() => { adminGeneralSecurityContainer.switchIsSamlEnabled() }}
+                disabled={adminSamlSecurityContainer.state.useOnlyEnvVars}
               />
               <label htmlFor="isSamlEnabled">
                 {t('security_setting.SAML.enable_saml')}
@@ -469,7 +470,7 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
               <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 %}">
+            <table className={`table settings-table ${useOnlyEnvVars && 'use-only-env-vars'}`}>
               <colgroup>
                 <col className="item-name" />
                 <col className="from-db" />

+ 12 - 14
src/client/js/components/Admin/Security/SecuritySetting.jsx

@@ -46,11 +46,6 @@ class SecuritySetting extends React.Component {
   render() {
     const { t, adminGeneralSecurityContainer } = this.props;
     const { currentRestrictGuestMode, currentPageCompleteDeletionAuthority } = adminGeneralSecurityContainer.state;
-    const helpPageListingByOwner = { __html: t('security_setting.page_listing_1') };
-    const helpPageListingByGroup = { __html: t('security_setting.page_listing_2') };
-    // eslint-disable-next-line max-len
-    const helpForceWikiMode = { __html: t('security_setting.Fixed by env var', { forcewikimode: 'FORCE_WIKI_MODE', wikimode: adminGeneralSecurityContainer.state.wikiMode }) };
-
 
     return (
       <React.Fragment>
@@ -63,18 +58,17 @@ class SecuritySetting extends React.Component {
               <p>{t('Error occurred')} : {this.state.retrieveError}</p>
             </div>
           )}
-          <div className="row mb-5">
+          <div className="row">
             <strong className="col-xs-3 text-right"> {t('security_setting.Guest Users Access')} </strong>
             <div className="col-xs-9 text-left">
               <div className="my-0 btn-group">
                 <div className="dropdown">
                   <button
-                    className="btn btn-default dropdown-toggle w-100"
+                    className={`btn btn-default dropdown-toggle w-100 ${adminGeneralSecurityContainer.isWikiModeForced && 'disabled'}`}
                     type="button"
                     data-toggle="dropdown"
                     aria-haspopup="true"
                     aria-expanded="false"
-                    disabled={adminGeneralSecurityContainer.state.isWikiModeForced}
                   >
                     <span className="pull-left">
                       {currentRestrictGuestMode === 'Deny' && t('security_setting.guest_mode.deny')}
@@ -107,20 +101,24 @@ class SecuritySetting extends React.Component {
               </div>
             </div>
           </div>
-          {adminGeneralSecurityContainer.state.isWikiModeForced && (
+          {adminGeneralSecurityContainer.isWikiModeForced && (
             <div className="row mb-5">
-              <div className="col-xs-3 text-right" />
-              <div className="col-xs-9 text-left">
+              <div className="col-xs-offset-3 col-xs-6 text-left">
                 <p className="alert alert-warning mt-2 text-left">
                   <i className="icon-exclamation icon-fw">
                   </i><b>FIXED</b><br />
-                  {<b dangerouslySetInnerHTML={helpForceWikiMode} />}
+                  <b
+                    dangerouslySetInnerHTML={{
+                    __html: t('security_setting.Fixed by env var',
+                    { forcewikimode: 'FORCE_WIKI_MODE', wikimode: adminGeneralSecurityContainer.state.wikiMode }),
+                    }}
+                  />
                 </p>
               </div>
             </div>
           )}
           <div className="row mb-5">
-            <strong className="col-xs-3 text-right" dangerouslySetInnerHTML={helpPageListingByOwner} />
+            <strong className="col-xs-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_1') }} />
             <div className="col-xs-6 text-left">
               <div className="checkbox checkbox-success">
                 <input
@@ -137,7 +135,7 @@ class SecuritySetting extends React.Component {
           </div>
 
           <div className="row mb-5">
-            <strong className="col-xs-3 text-right" dangerouslySetInnerHTML={helpPageListingByGroup} />
+            <strong className="col-xs-3 text-right" dangerouslySetInnerHTML={{ __html: t('security_setting.page_listing_2') }} />
             <div className="col-xs-6 text-left">
               <div className="checkbox checkbox-success">
                 <input

+ 1 - 1
src/client/js/components/Page/TagEditor.jsx

@@ -47,7 +47,7 @@ export default class TagEditor extends React.Component {
   render() {
     return (
       <Modal isOpen={this.state.isOpenModal} toggle={this.closeModalHandler} id="editTagModal">
-        <ModalHeader closeButton className="bg-primary">
+        <ModalHeader tag="h4" toggle={this.closeModalHandler} className="bg-primary">
           <span className="text-white">Edit Tags</span>
         </ModalHeader>
         <ModalBody>

+ 8 - 14
src/client/js/services/AdminGeneralSecurityContainer.js

@@ -15,13 +15,11 @@ export default class AdminGeneralSecurityContainer extends Container {
     this.appContainer = appContainer;
 
     this.state = {
-      isWikiModeForced: false,
       wikiMode: '',
       currentRestrictGuestMode: 'Deny',
       currentPageCompleteDeletionAuthority: 'adminOnly',
       isShowRestrictedByOwner: false,
       isShowRestrictedByGroup: false,
-      useOnlyEnvVarsForSomeOptions: false,
       appSiteUrl: appContainer.config.crowi.url || '',
       isLocalEnabled: false,
       isLdapEnabled: false,
@@ -34,14 +32,12 @@ export default class AdminGeneralSecurityContainer extends Container {
       setupStrategies: [],
     };
 
-    this.onIsWikiModeForced = this.onIsWikiModeForced.bind(this);
   }
 
   async retrieveSecurityData() {
     await this.retrieveSetupStratedies();
     const response = await this.appContainer.apiv3.get('/security-setting/');
     const { generalSetting, generalAuth } = response.data.securityParams;
-    this.onIsWikiModeForced(generalSetting.wikiMode);
     this.setState({
       currentPageCompleteDeletionAuthority: generalSetting.pageCompleteDeletionAuthority,
       isShowRestrictedByOwner: !generalSetting.hideRestrictedByOwner,
@@ -66,6 +62,14 @@ export default class AdminGeneralSecurityContainer extends Container {
     return 'AdminGeneralSecurityContainer';
   }
 
+  /**
+   * get isWikiModeForced
+   * @return {bool} isWikiModeForced
+   */
+  get isWikiModeForced() {
+    return this.state.wikiMode === 'public' || this.state.wikiMode === 'private';
+  }
+
   /**
    * Change restrictGuestMode
    */
@@ -94,16 +98,6 @@ export default class AdminGeneralSecurityContainer extends Container {
     this.setState({ isShowRestrictedByGroup:  !this.state.isShowRestrictedByGroup });
   }
 
-  onIsWikiModeForced(wikiModeSetting) {
-    if (wikiModeSetting === 'private') {
-      this.setState({ isWikiModeForced: true });
-    }
-    else {
-      this.setState({ isWikiModeForced: false });
-    }
-  }
-
-
   /**
    * Update restrictGuestMode
    * @memberOf AdminGeneralSecuritySContainer

+ 2 - 0
src/client/js/services/AdminLocalSecurityContainer.js

@@ -18,6 +18,7 @@ export default class AdminLocalSecurityContainer extends Container {
       retrieveError: null,
       registrationMode: 'Open',
       registrationWhiteList: [],
+      useOnlyEnvVars: false,
     };
 
   }
@@ -27,6 +28,7 @@ export default class AdminLocalSecurityContainer extends Container {
       const response = await this.appContainer.apiv3.get('/security-setting/');
       const { localSetting } = response.data.securityParams;
       this.setState({
+        useOnlyEnvVars: localSetting.useOnlyEnvVarsForSomeOptions,
         registrationMode: localSetting.registrationMode,
         registrationWhiteList: localSetting.registrationWhiteList,
       });

+ 2 - 0
src/client/js/services/AdminSamlSecurityContainer.js

@@ -21,6 +21,7 @@ export default class AdminSamlSecurityContainer extends Container {
 
     this.state = {
       retrieveError: null,
+      // TODO GW-1324 ABLCRure DB value takes precedence
       useOnlyEnvVars: false,
       callbackUrl: urljoin(pathUtils.removeTrailingSlash(appContainer.config.crowi.url), '/passport/saml/callback'),
       missingMandatoryConfigKeys: [],
@@ -48,6 +49,7 @@ export default class AdminSamlSecurityContainer extends Container {
       const { samlAuth } = response.data.securityParams;
       this.setState({
         missingMandatoryConfigKeys: samlAuth.missingMandatoryConfigKeys,
+        useOnlyEnvVars: samlAuth.useOnlyEnvVarsForSomeOptions,
         samlEntryPoint: samlAuth.samlEntryPoint,
         samlIssuer: samlAuth.samlIssuer,
         samlCert: samlAuth.samlCert,

+ 1 - 1
src/client/styles/scss/_vendor.scss

@@ -2,7 +2,7 @@
 @import '~bootstrap/scss/bootstrap';
 
 // import react-bootstrap-typeahead
-@import '~react-bootstrap-typeahead/css/Typeahead-bs4';
+@import '~react-bootstrap-typeahead/css/Typeahead';
 
 // import toastr styles
 @import '~toastr/build/toastr';

+ 3 - 0
src/lib/service/logger/stream.prod.js

@@ -17,6 +17,9 @@ else {
     const bunyanFormat = require('bunyan-format');
     stream = bunyanFormat({ outputMode: 'long' });
   }
+  else {
+    stream = process.stdout;
+  }
 }
 
 module.exports = stream;

+ 2 - 2
src/server/crowi/express-init.js

@@ -19,7 +19,7 @@ module.exports = function(crowi, app) {
   const i18nSprintf = require('i18next-sprintf-postprocessor');
   const i18nMiddleware = require('i18next-express-middleware');
 
-  const safeRedirect = require('../middleware/safe-redirect')();
+  const registerSafeRedirect = require('../middleware/safe-redirect')();
 
   const avoidSessionRoutes = require('../routes/avoid-session-routes');
   const i18nUserSettingDetector = require('../util/i18nUserSettingDetector');
@@ -115,7 +115,7 @@ module.exports = function(crowi, app) {
 
   app.use(flash());
 
-  app.use(safeRedirect);
+  app.use(registerSafeRedirect);
 
   const middlewares = require('../util/middlewares')(crowi, app);
 

+ 4 - 2
src/server/routes/apiv3/security-setting.js

@@ -322,6 +322,7 @@ module.exports = (crowi) => {
         wikiMode: await crowi.configManager.getConfig('crowi', 'security:wikiMode'),
       },
       localSetting: {
+        useOnlyEnvVarsForSomeOptions: await crowi.configManager.getConfig('crowi', 'security:passport-local:useOnlyEnvVarsForSomeOptions'),
         registrationMode: await crowi.configManager.getConfig('crowi', 'security:registrationMode'),
         registrationWhiteList: await crowi.configManager.getConfig('crowi', 'security:registrationWhiteList'),
       },
@@ -351,6 +352,7 @@ module.exports = (crowi) => {
       },
       samlAuth: {
         missingMandatoryConfigKeys: await crowi.passportService.getSamlMissingMandatoryConfigKeys(),
+        useOnlyEnvVarsForSomeOptions: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:useOnlyEnvVarsForSomeOptions'),
         samlEntryPoint: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:entryPoint'),
         samlEnvVarEntryPoint: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:entryPoint'),
         samlIssuer: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:issuer'),
@@ -369,7 +371,7 @@ module.exports = (crowi) => {
         samlEnvVarAttrMapLastName: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:attrMapLastName'),
         isSameUsernameTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameUsernameTreatedAsIdenticalUser'),
         isSameEmailTreatedAsIdenticalUser: await crowi.configManager.getConfig('crowi', 'security:passport-saml:isSameEmailTreatedAsIdenticalUser'),
-        samlABLCRule: await crowi.configManager.getConfig('crowi', 'security:passport-saml:ABLCRule'),
+        samlABLCRule: await crowi.configManager.getConfigFromDB('crowi', 'security:passport-saml:ABLCRule'),
         samlEnvVarABLCRule: await crowi.configManager.getConfigFromEnvVars('crowi', 'security:passport-saml:ABLCRule'),
       },
       oidcAuth: {
@@ -524,7 +526,7 @@ module.exports = (crowi) => {
       'security:list-policy:hideRestrictedByGroup': req.body.hideRestrictedByGroup,
     };
     const wikiMode = await crowi.configManager.getConfig('crowi', 'security:wikiMode');
-    if (wikiMode === 'private') {
+    if (wikiMode === 'private' || wikiMode === 'public') {
       logger.debug('security:restrictGuestMode will not be changed because wiki mode is forced to set');
       delete requestParams['security:restrictGuestMode'];
     }

+ 2 - 1
src/server/routes/login.js

@@ -56,7 +56,8 @@ module.exports = function(crowi, app) {
 
   actions.preLogin = function(req, res, next) {
     // user has already logged in
-    if (req.user != null) {
+    const { user } = req;
+    if (user != null && user.status === User.STATUS_ACTIVE) {
       const { redirectTo } = req.session;
       // remove session.redirectTo
       delete req.session.redirectTo;

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

@@ -684,6 +684,7 @@ class PassportService {
   verifySAMLResponseByABLCRule(response) {
     const rule = this.crowi.configManager.getConfig('crowi', 'security:passport-saml:ABLCRule');
     if (rule == null) {
+      debug('There is no ABLCRule.');
       return true;
     }
 

+ 8 - 8
src/test/middleware/safe-redirect.test.js

@@ -1,7 +1,7 @@
 /* eslint-disable arrow-body-style */
 
 describe('safeRedirect', () => {
-  let safeRedirect;
+  let registerSafeRedirect;
 
   const whitelistOfHosts = [
     'white1.example.com:8080',
@@ -9,7 +9,7 @@ describe('safeRedirect', () => {
   ];
 
   beforeEach(async(done) => {
-    safeRedirect = require('@server/middleware/safe-redirect')(whitelistOfHosts);
+    registerSafeRedirect = require('@server/middleware/safe-redirect')(whitelistOfHosts);
     done();
   });
 
@@ -26,7 +26,7 @@ describe('safeRedirect', () => {
     const next = jest.fn();
 
     test('redirects to \'/\' because specified url causes open redirect vulnerability', () => {
-      safeRedirect(req, res, next);
+      registerSafeRedirect(req, res, next);
 
       const result = res.safeRedirect('//evil.example.com');
 
@@ -39,7 +39,7 @@ describe('safeRedirect', () => {
     });
 
     test('redirects to \'/\' because specified host without port is not in whitelist', () => {
-      safeRedirect(req, res, next);
+      registerSafeRedirect(req, res, next);
 
       const result = res.safeRedirect('http://white1.example.com/path/to/page');
 
@@ -52,7 +52,7 @@ describe('safeRedirect', () => {
     });
 
     test('redirects to the specified local url', () => {
-      safeRedirect(req, res, next);
+      registerSafeRedirect(req, res, next);
 
       const result = res.safeRedirect('/path/to/page');
 
@@ -65,7 +65,7 @@ describe('safeRedirect', () => {
     });
 
     test('redirects to the specified local url (fqdn)', () => {
-      safeRedirect(req, res, next);
+      registerSafeRedirect(req, res, next);
 
       const result = res.safeRedirect('http://example.com/path/to/page');
 
@@ -78,7 +78,7 @@ describe('safeRedirect', () => {
     });
 
     test('redirects to the specified whitelisted url (white1.example.com:8080)', () => {
-      safeRedirect(req, res, next);
+      registerSafeRedirect(req, res, next);
 
       const result = res.safeRedirect('http://white1.example.com:8080/path/to/page');
 
@@ -91,7 +91,7 @@ describe('safeRedirect', () => {
     });
 
     test('redirects to the specified whitelisted url (white2.example.com:8080)', () => {
-      safeRedirect(req, res, next);
+      registerSafeRedirect(req, res, next);
 
       const result = res.safeRedirect('http://white2.example.com:8080/path/to/page');