* 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
@@ -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; | |||
} | |||
@@ -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; |
@@ -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; | |||
} |
@@ -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); |
@@ -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(); | |||
}); |
@@ -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> | |||
`; |
@@ -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: ( |
@@ -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> | |||
#------------------------------------------------------------------------------ |