Browse Source

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.
tags/7.5
wouter-admiraal-sonarsource 5 years ago
parent
commit
36efcfabb5

+ 2
- 1
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css View 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;
}


+ 18
- 0
server/sonar-web/src/main/js/app/styles/init/misc.css View 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;

+ 13
- 0
server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.css View File

@@ -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;
}

+ 66
- 33
server/sonar-web/src/main/js/apps/sessions/components/LoginSonarCloud.tsx View File

@@ -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);

+ 13
- 1
server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginSonarCloud-test.tsx View 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();
});

+ 147
- 70
server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginSonarCloud-test.tsx.snap View File

@@ -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>
`;

+ 1
- 1
server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx View 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: (

+ 2
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties View 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>


#------------------------------------------------------------------------------

Loading…
Cancel
Save