]> source.dussan.org Git - sonarqube.git/commitdiff
Hardening dec 5 (#1030)
authorwouter-admiraal-sonarsource <45544358+wouter-admiraal-sonarsource@users.noreply.github.com>
Mon, 10 Dec 2018 07:47:36 +0000 (08:47 +0100)
committerSonarTech <sonartech@sonarsource.com>
Mon, 10 Dec 2018 19:21:00 +0000 (20:21 +0100)
* SONAR-11533 Drop obsolete bullet in 'How To Use' section for encryption

* SONAR-11541 Give branch and PR names more room in dropdown

* SONAR-11469 Display an Alert when redirected to login

When a user is redirected to the login page after trying to access a
page which she doesn't have sufficient permissions for, display an
alert explaining why the user was redirected, and provide a link to go
back to the homepage.

server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
server/sonar-web/src/main/js/app/styles/init/misc.css
server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.css
server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.tsx
server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginSonarCloud-test.tsx
server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginSonarCloud-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 50141bd6c0cb797bd37e85ce6b65d312cacda6de..a9eaf7e918b9fe830cda597503b3781b18b3685d 100644 (file)
@@ -31,6 +31,7 @@
 
 .navbar-context-branches .popup {
   min-width: 430px;
+  max-width: 650px;
 }
 
 .navbar-context-meta-branch-menu-title {
@@ -44,7 +45,7 @@
 }
 
 .navbar-context-meta-branch-menu-item-name {
-  flex: 0 1 300px; /* Workaround for SONAR-10971 */
+  flex: 0 1 550px; /* Workaround for SONAR-10971 */
   min-width: 0;
 }
 
index e8cc9b5fa24a1585a3b14f2b19543e01a3c56691..6c7364ab3ecb22318fb392eceeca20c50454db5d 100644 (file)
@@ -372,6 +372,24 @@ td.big-spacer-top {
   color: rgba(68, 68, 68, 0.3);
 }
 
+.horizontal-pipe-separator {
+  display: flex;
+  align-items: center;
+  margin-top: calc(4 * var(--gridSize));
+  margin-bottom: calc(4 * var(--gridSize));
+}
+
+.horizontal-pipe-separator > .horizontal-separator {
+  margin: 0 4px;
+}
+
+.horizontal-separator {
+  min-width: 16px;
+  height: 1px;
+  flex-grow: 1;
+  background-color: var(--barBorderColor);
+}
+
 .vertical-separator {
   width: 1px;
   min-height: 16px;
index 5f724dfc73d9c50b4afc535eb6adfcf957af7c86..aace3eb01ed8123fbbfef03079465fe895fbe84c 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+.sonarcloud-login-alert {
+  margin: 10vh auto 5vh auto;
+  width: 256px;
+}
+
 .sonarcloud-login-page {
   margin-top: 15vh;
   width: 216px;
   padding: calc(4 * var(--gridSize)) 20px;
 }
 
+.sonarcloud-login-alert ~ .sonarcloud-login-page {
+  margin-top: 0;
+}
+
 .sonarcloud-login-page-large {
   width: 300px;
 }
@@ -48,3 +57,7 @@
 .sonarcloud-oauth-providers.oauth-providers .oauth-providers-help {
   right: -22px;
 }
+
+.sonarcloud-login-cancel {
+  text-align: center;
+}
index ce758229acc80f969b23947c5be3cc7bbd64b2f7..2f40d8724ab9f1d0151a35f6baea1ec2c0aa7824 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import { connect } from 'react-redux';
 import * as classNames from 'classnames';
 import LoginForm from './LoginForm';
 import OAuthProviders from './OAuthProviders';
 import { getBaseUrl } from '../../../helpers/urls';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { Alert } from '../../../components/ui/Alert';
 import './LoginSonarCloud.css';
+import { Store } from '../../../store/rootReducer';
 
 interface Props {
   identityProviders: T.IdentityProvider[];
   onSubmit: (login: string, password: string) => Promise<void>;
   returnTo: string;
   showForm?: boolean;
+  authorizationError?: boolean;
+  authenticationError?: boolean;
 }
 
-export default function LoginSonarCloud({
+function formatLabel(name: string) {
+  return translateWithParameters('login.with_x', name);
+}
+
+export function LoginSonarCloud({
+  showForm,
   identityProviders,
-  onSubmit,
   returnTo,
-  showForm
+  onSubmit,
+  authorizationError,
+  authenticationError
 }: Props) {
   const displayForm = showForm || identityProviders.length <= 0;
+  const displayErrorAction = authorizationError || authenticationError;
   return (
-    <div
-      className={classNames('sonarcloud-login-page boxed-group boxed-group-inner', {
-        'sonarcloud-login-page-large': displayForm
-      })}
-      id="login_form">
-      <div className="text-center">
-        <img
-          alt="SonarCloud logo"
-          height={36}
-          src={`${getBaseUrl()}/images/sonarcloud-square-logo.svg`}
-          width={36}
-        />
-        <h1 className="sonarcloud-login-title">
-          {translate('login.login_or_signup_to_sonarcloud')}
-        </h1>
-      </div>
-
-      {displayForm ? (
-        <LoginForm onSubmit={onSubmit} returnTo={returnTo} />
-      ) : (
-        <OAuthProviders
-          className="sonarcloud-oauth-providers"
-          formatLabel={formatLabel}
-          identityProviders={identityProviders}
-          returnTo={returnTo}
-        />
+    <>
+      {displayErrorAction && (
+        <Alert className="sonarcloud-login-alert" display="block" variant="warning">
+          {translate('login.unauthorized_access_alert')}
+        </Alert>
       )}
-    </div>
+      <div
+        className={classNames('sonarcloud-login-page boxed-group boxed-group-inner', {
+          'sonarcloud-login-page-large': displayForm
+        })}
+        id="login_form">
+        <div className="text-center">
+          <img
+            alt="SonarCloud logo"
+            height={36}
+            src={`${getBaseUrl()}/images/sonarcloud-square-logo.svg`}
+            width={36}
+          />
+          <h1 className="sonarcloud-login-title">
+            {translate('login.login_or_signup_to_sonarcloud')}
+          </h1>
+        </div>
+
+        {displayForm ? (
+          <LoginForm onSubmit={onSubmit} returnTo={returnTo} />
+        ) : (
+          <OAuthProviders
+            className="sonarcloud-oauth-providers"
+            formatLabel={formatLabel}
+            identityProviders={identityProviders}
+            returnTo={returnTo}
+          />
+        )}
+
+        {displayErrorAction && (
+          <div className="sonarcloud-login-cancel">
+            <div className="horizontal-pipe-separator">
+              <div className="horizontal-separator" />
+              <span className="note">{translate('or')}</span>
+              <div className="horizontal-separator" />
+            </div>
+            <a href={`${getBaseUrl()}/`}>{translate('go_back_to_homepage')}</a>
+          </div>
+        )}
+      </div>
+    </>
   );
 }
 
-function formatLabel(name: string) {
-  return translateWithParameters('login.with_x', name);
-}
+const mapStateToProps = (state: Store) => ({
+  authorizationError: state.appState.authorizationError,
+  authenticationError: state.appState.authenticationError
+});
+
+export default connect(mapStateToProps)(LoginSonarCloud);
index f29a3d8b963f5b20493ed7c2b13ecac6e6a44fd6..b2a00661ed466bf7532846e775dff5318b91574a 100644 (file)
@@ -19,7 +19,7 @@
  */
 import * as React from 'react';
 import { shallow } from 'enzyme';
-import LoginSonarCloud from '../LoginSonarCloud';
+import { LoginSonarCloud } from '../LoginSonarCloud';
 
 const identityProvider = {
   backgroundColor: '#000',
@@ -50,3 +50,15 @@ it('logs in with simple form', () => {
     shallow(<LoginSonarCloud identityProviders={[]} onSubmit={jest.fn()} returnTo="" />)
   ).toMatchSnapshot();
 });
+
+it("shows an warning message if there's an authorization error", () => {
+  const wrapper = shallow(
+    <LoginSonarCloud
+      authorizationError={true}
+      identityProviders={[identityProvider]}
+      onSubmit={jest.fn()}
+      returnTo=""
+    />
+  );
+  expect(wrapper).toMatchSnapshot();
+});
index cd3b0f00ea8e063a992600e4c970f2ac9afd3ac4..56308c07525a73848274b2fa13570a0bf8d5f063 100644 (file)
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`logs in with identity provider 1`] = `
-<div
-  className="sonarcloud-login-page boxed-group boxed-group-inner"
-  id="login_form"
->
+<Fragment>
   <div
-    className="text-center"
+    className="sonarcloud-login-page boxed-group boxed-group-inner"
+    id="login_form"
   >
-    <img
-      alt="SonarCloud logo"
-      height={36}
-      src="/images/sonarcloud-square-logo.svg"
-      width={36}
-    />
-    <h1
-      className="sonarcloud-login-title"
+    <div
+      className="text-center"
     >
-      login.login_or_signup_to_sonarcloud
-    </h1>
+      <img
+        alt="SonarCloud logo"
+        height={36}
+        src="/images/sonarcloud-square-logo.svg"
+        width={36}
+      />
+      <h1
+        className="sonarcloud-login-title"
+      >
+        login.login_or_signup_to_sonarcloud
+      </h1>
+    </div>
+    <OAuthProviders
+      className="sonarcloud-oauth-providers"
+      formatLabel={[Function]}
+      identityProviders={
+        Array [
+          Object {
+            "backgroundColor": "#000",
+            "iconPath": "/some/path",
+            "key": "foo",
+            "name": "foo",
+          },
+        ]
+      }
+      returnTo=""
+    />
   </div>
-  <OAuthProviders
-    className="sonarcloud-oauth-providers"
-    formatLabel={[Function]}
-    identityProviders={
-      Array [
-        Object {
-          "backgroundColor": "#000",
-          "iconPath": "/some/path",
-          "key": "foo",
-          "name": "foo",
-        },
-      ]
-    }
-    returnTo=""
-  />
-</div>
+</Fragment>
 `;
 
 exports[`logs in with simple form 1`] = `
-<div
-  className="sonarcloud-login-page boxed-group boxed-group-inner sonarcloud-login-page-large"
-  id="login_form"
->
+<Fragment>
   <div
-    className="text-center"
+    className="sonarcloud-login-page boxed-group boxed-group-inner sonarcloud-login-page-large"
+    id="login_form"
   >
-    <img
-      alt="SonarCloud logo"
-      height={36}
-      src="/images/sonarcloud-square-logo.svg"
-      width={36}
-    />
-    <h1
-      className="sonarcloud-login-title"
+    <div
+      className="text-center"
     >
-      login.login_or_signup_to_sonarcloud
-    </h1>
+      <img
+        alt="SonarCloud logo"
+        height={36}
+        src="/images/sonarcloud-square-logo.svg"
+        width={36}
+      />
+      <h1
+        className="sonarcloud-login-title"
+      >
+        login.login_or_signup_to_sonarcloud
+      </h1>
+    </div>
+    <LoginForm
+      onSubmit={[MockFunction]}
+      returnTo=""
+    />
   </div>
-  <LoginForm
-    onSubmit={[MockFunction]}
-    returnTo=""
-  />
-</div>
+</Fragment>
 `;
 
 exports[`logs in with simple form 2`] = `
-<div
-  className="sonarcloud-login-page boxed-group boxed-group-inner sonarcloud-login-page-large"
-  id="login_form"
->
+<Fragment>
   <div
-    className="text-center"
+    className="sonarcloud-login-page boxed-group boxed-group-inner sonarcloud-login-page-large"
+    id="login_form"
   >
-    <img
-      alt="SonarCloud logo"
-      height={36}
-      src="/images/sonarcloud-square-logo.svg"
-      width={36}
+    <div
+      className="text-center"
+    >
+      <img
+        alt="SonarCloud logo"
+        height={36}
+        src="/images/sonarcloud-square-logo.svg"
+        width={36}
+      />
+      <h1
+        className="sonarcloud-login-title"
+      >
+        login.login_or_signup_to_sonarcloud
+      </h1>
+    </div>
+    <LoginForm
+      onSubmit={[MockFunction]}
+      returnTo=""
+    />
+  </div>
+</Fragment>
+`;
+
+exports[`shows an warning message if there's an authorization error 1`] = `
+<Fragment>
+  <Alert
+    className="sonarcloud-login-alert"
+    display="block"
+    variant="warning"
+  >
+    login.unauthorized_access_alert
+  </Alert>
+  <div
+    className="sonarcloud-login-page boxed-group boxed-group-inner"
+    id="login_form"
+  >
+    <div
+      className="text-center"
+    >
+      <img
+        alt="SonarCloud logo"
+        height={36}
+        src="/images/sonarcloud-square-logo.svg"
+        width={36}
+      />
+      <h1
+        className="sonarcloud-login-title"
+      >
+        login.login_or_signup_to_sonarcloud
+      </h1>
+    </div>
+    <OAuthProviders
+      className="sonarcloud-oauth-providers"
+      formatLabel={[Function]}
+      identityProviders={
+        Array [
+          Object {
+            "backgroundColor": "#000",
+            "iconPath": "/some/path",
+            "key": "foo",
+            "name": "foo",
+          },
+        ]
+      }
+      returnTo=""
     />
-    <h1
-      className="sonarcloud-login-title"
+    <div
+      className="sonarcloud-login-cancel"
     >
-      login.login_or_signup_to_sonarcloud
-    </h1>
+      <div
+        className="horizontal-pipe-separator"
+      >
+        <div
+          className="horizontal-separator"
+        />
+        <span
+          className="note"
+        >
+          or
+        </span>
+        <div
+          className="horizontal-separator"
+        />
+      </div>
+      <a
+        href="/"
+      >
+        go_back_to_homepage
+      </a>
+    </div>
   </div>
-  <LoginForm
-    onSubmit={[MockFunction]}
-    returnTo=""
-  />
-</div>
+</Fragment>
 `;
index 1855fe22d32213825242f5d6f981a5843ed2b03b..7f1136ada2fd92ca9e3930861c52d638426275bd 100644 (file)
@@ -85,7 +85,7 @@ export default class GenerateSecretKeyForm extends React.PureComponent<Props, St
           <form id="generate-secret-key-form" onSubmit={this.handleSubmit}>
             <p className="spacer-bottom">
               <FormattedMessage
-                defaultMessage={translate('encryptionFormattedMessage.secret_key_description')}
+                defaultMessage={translate('encryption.secret_key_description')}
                 id="encryption.secret_key_description"
                 values={{
                   moreInformationLink: (
index da2074822072861576416b8fc437d3c63b1e4230..f719e11c23bece44818fd379be35bb9c73b9cb50 100644 (file)
@@ -1461,6 +1461,7 @@ login.login_to_sonarqube=Log In to SonarQube
 login.login_or_signup_to_sonarcloud=Log in or Sign up to SonarCloud
 login.login_with_x=Log in with {0}
 login.more_options=More options
+login.unauthorized_access_alert=You are not authorized to access this page. Please log in with more privileges and try again.
 login.with_x=With {0}
 
 unauthorized.message=You're not authorized to access this page. Please contact the administrator.
@@ -2970,7 +2971,7 @@ encryption.encrypt=Encrypt
 encryption.secret_key_description=Secret key is required to be able to encrypt properties. {moreInformationLink}
 encryption.secret_key=Secret Key
 encryption.how_to_use=How To Use
-encryption.how_to_use.content=<ul><li>Store the secret key in the file <code>~/.sonar/sonar-secret.txt</code> of the server. This file can be relocated by defining the property <code>sonar.secretKeyPath</code> in <code>conf/sonar.properties</code></li><li>Restrict access to this file by making it readable and by owner only</li><li>Restart the server if the property <code>sonar.secretKeyPath</code> has been set or changed.</li><li>Copy this file on all the machines that execute code inspection. Define the property <code>sonar.secretKeyPath</code> on those machines if the path is not <code>~/.sonar/sonar-secret.txt</code>.</li><li>For each property that you want to encrypt, generate the encrypted value and replace the original value wherever it is stored (configuration files, command lines).</li></ul>
+encryption.how_to_use.content=<ul><li>Store the secret key in the file <code>~/.sonar/sonar-secret.txt</code> of the server. This file can be relocated by defining the property <code>sonar.secretKeyPath</code> in <code>conf/sonar.properties</code></li><li>Restrict access to this file by making it readable and by owner only</li><li>Restart the server if the property <code>sonar.secretKeyPath</code> has been set or changed.</li><li>For each property that you want to encrypt, generate the encrypted value and replace the original value wherever it is stored (configuration files, command lines).</li></ul>
 
 
 #------------------------------------------------------------------------------