diff options
author | wouter-admiraal-sonarsource <45544358+wouter-admiraal-sonarsource@users.noreply.github.com> | 2018-12-10 08:47:36 +0100 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-12-10 20:21:00 +0100 |
commit | 36efcfabb50b8fd9086ec906d733a159a41e5367 (patch) | |
tree | ab8ca5a3bc80cd416af8123953165ab0b5a5e21d | |
parent | accbb5561aaad5814873806858258eb5fe89e270 (diff) | |
download | sonarqube-36efcfabb50b8fd9086ec906d733a159a41e5367.tar.gz sonarqube-36efcfabb50b8fd9086ec906d733a159a41e5367.zip |
Hardening dec 5 (#1030)
* 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.
8 files changed, 262 insertions, 107 deletions
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css index 50141bd6c0c..a9eaf7e918b 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css @@ -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; } diff --git a/server/sonar-web/src/main/js/app/styles/init/misc.css b/server/sonar-web/src/main/js/app/styles/init/misc.css index e8cc9b5fa24..6c7364ab3ec 100644 --- a/server/sonar-web/src/main/js/app/styles/init/misc.css +++ b/server/sonar-web/src/main/js/app/styles/init/misc.css @@ -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; diff --git a/server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.css b/server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.css index 5f724dfc73d..aace3eb01ed 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.css +++ b/server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.css @@ -17,6 +17,11 @@ * 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; @@ -25,6 +30,10 @@ 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; +} diff --git a/server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.tsx b/server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.tsx index ce758229acc..2f40d8724ab 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.tsx @@ -18,59 +18,92 @@ * 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); diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginSonarCloud-test.tsx b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginSonarCloud-test.tsx index f29a3d8b963..b2a00661ed4 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginSonarCloud-test.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginSonarCloud-test.tsx @@ -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(); +}); diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginSonarCloud-test.tsx.snap b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginSonarCloud-test.tsx.snap index cd3b0f00ea8..56308c07525 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginSonarCloud-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginSonarCloud-test.tsx.snap @@ -1,93 +1,170 @@ // 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> `; diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx b/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx index 1855fe22d32..7f1136ada2f 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx @@ -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: ( diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index da207482207..f719e11c23b 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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> #------------------------------------------------------------------------------ |