* 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
.navbar-context-branches .popup { | .navbar-context-branches .popup { | ||||
min-width: 430px; | min-width: 430px; | ||||
max-width: 650px; | |||||
} | } | ||||
.navbar-context-meta-branch-menu-title { | .navbar-context-meta-branch-menu-title { | ||||
} | } | ||||
.navbar-context-meta-branch-menu-item-name { | .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; | min-width: 0; | ||||
} | } | ||||
color: rgba(68, 68, 68, 0.3); | 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 { | .vertical-separator { | ||||
width: 1px; | width: 1px; | ||||
min-height: 16px; | min-height: 16px; |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
.sonarcloud-login-alert { | |||||
margin: 10vh auto 5vh auto; | |||||
width: 256px; | |||||
} | |||||
.sonarcloud-login-page { | .sonarcloud-login-page { | ||||
margin-top: 15vh; | margin-top: 15vh; | ||||
width: 216px; | width: 216px; | ||||
padding: calc(4 * var(--gridSize)) 20px; | padding: calc(4 * var(--gridSize)) 20px; | ||||
} | } | ||||
.sonarcloud-login-alert ~ .sonarcloud-login-page { | |||||
margin-top: 0; | |||||
} | |||||
.sonarcloud-login-page-large { | .sonarcloud-login-page-large { | ||||
width: 300px; | width: 300px; | ||||
} | } | ||||
.sonarcloud-oauth-providers.oauth-providers .oauth-providers-help { | .sonarcloud-oauth-providers.oauth-providers .oauth-providers-help { | ||||
right: -22px; | right: -22px; | ||||
} | } | ||||
.sonarcloud-login-cancel { | |||||
text-align: center; | |||||
} |
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { connect } from 'react-redux'; | |||||
import * as classNames from 'classnames'; | import * as classNames from 'classnames'; | ||||
import LoginForm from './LoginForm'; | import LoginForm from './LoginForm'; | ||||
import OAuthProviders from './OAuthProviders'; | import OAuthProviders from './OAuthProviders'; | ||||
import { getBaseUrl } from '../../../helpers/urls'; | import { getBaseUrl } from '../../../helpers/urls'; | ||||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | import { translate, translateWithParameters } from '../../../helpers/l10n'; | ||||
import { Alert } from '../../../components/ui/Alert'; | |||||
import './LoginSonarCloud.css'; | import './LoginSonarCloud.css'; | ||||
import { Store } from '../../../store/rootReducer'; | |||||
interface Props { | interface Props { | ||||
identityProviders: T.IdentityProvider[]; | identityProviders: T.IdentityProvider[]; | ||||
onSubmit: (login: string, password: string) => Promise<void>; | onSubmit: (login: string, password: string) => Promise<void>; | ||||
returnTo: string; | returnTo: string; | ||||
showForm?: boolean; | 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, | identityProviders, | ||||
onSubmit, | |||||
returnTo, | returnTo, | ||||
showForm | |||||
onSubmit, | |||||
authorizationError, | |||||
authenticationError | |||||
}: Props) { | }: Props) { | ||||
const displayForm = showForm || identityProviders.length <= 0; | const displayForm = showForm || identityProviders.length <= 0; | ||||
const displayErrorAction = authorizationError || authenticationError; | |||||
return ( | 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); |
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import LoginSonarCloud from '../LoginSonarCloud'; | |||||
import { LoginSonarCloud } from '../LoginSonarCloud'; | |||||
const identityProvider = { | const identityProvider = { | ||||
backgroundColor: '#000', | backgroundColor: '#000', | ||||
shallow(<LoginSonarCloud identityProviders={[]} onSubmit={jest.fn()} returnTo="" />) | shallow(<LoginSonarCloud identityProviders={[]} onSubmit={jest.fn()} returnTo="" />) | ||||
).toMatchSnapshot(); | ).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(); | |||||
}); |
// Jest Snapshot v1, https://goo.gl/fbAQLP | // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
exports[`logs in with identity provider 1`] = ` | exports[`logs in with identity provider 1`] = ` | ||||
<div | |||||
className="sonarcloud-login-page boxed-group boxed-group-inner" | |||||
id="login_form" | |||||
> | |||||
<Fragment> | |||||
<div | <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> | </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`] = ` | 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 | <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> | </div> | ||||
<LoginForm | |||||
onSubmit={[MockFunction]} | |||||
returnTo="" | |||||
/> | |||||
</div> | |||||
</Fragment> | |||||
`; | `; | ||||
exports[`logs in with simple form 2`] = ` | 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 | <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> | </div> | ||||
<LoginForm | |||||
onSubmit={[MockFunction]} | |||||
returnTo="" | |||||
/> | |||||
</div> | |||||
</Fragment> | |||||
`; | `; |
<form id="generate-secret-key-form" onSubmit={this.handleSubmit}> | <form id="generate-secret-key-form" onSubmit={this.handleSubmit}> | ||||
<p className="spacer-bottom"> | <p className="spacer-bottom"> | ||||
<FormattedMessage | <FormattedMessage | ||||
defaultMessage={translate('encryptionFormattedMessage.secret_key_description')} | |||||
defaultMessage={translate('encryption.secret_key_description')} | |||||
id="encryption.secret_key_description" | id="encryption.secret_key_description" | ||||
values={{ | values={{ | ||||
moreInformationLink: ( | moreInformationLink: ( |
login.login_or_signup_to_sonarcloud=Log in or Sign up to SonarCloud | login.login_or_signup_to_sonarcloud=Log in or Sign up to SonarCloud | ||||
login.login_with_x=Log in with {0} | login.login_with_x=Log in with {0} | ||||
login.more_options=More options | 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} | login.with_x=With {0} | ||||
unauthorized.message=You're not authorized to access this page. Please contact the administrator. | unauthorized.message=You're not authorized to access this page. Please contact the administrator. | ||||
encryption.secret_key_description=Secret key is required to be able to encrypt properties. {moreInformationLink} | encryption.secret_key_description=Secret key is required to be able to encrypt properties. {moreInformationLink} | ||||
encryption.secret_key=Secret Key | encryption.secret_key=Secret Key | ||||
encryption.how_to_use=How To Use | 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> | |||||
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ |