]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8714 Make authorization errors more noticeable
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Fri, 11 Dec 2020 10:51:09 +0000 (11:51 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 17 Dec 2020 20:08:01 +0000 (20:08 +0000)
server/sonar-web/src/main/js/apps/sessions/components/Login.css
server/sonar-web/src/main/js/apps/sessions/components/Login.tsx
server/sonar-web/src/main/js/apps/sessions/components/__tests__/Login-test.tsx
server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/Login-test.tsx.snap
server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginContainer-test.tsx.snap
server/sonar-web/src/main/js/store/globalMessages.ts

index 7ee05ac91865ff6a8630c243b148454d388210a2..32cdebc5b6d8095e85975c81d1de882c8fef6ae0 100644 (file)
  */
 .login-page {
   padding-top: 10vh;
+  max-width: 300px;
+  margin: 0 auto;
 }
 
 .login-title {
-  margin-bottom: 40px;
   line-height: 1.5;
   font-size: 24px;
   font-weight: 300;
-  text-align: center;
 }
index 0b3d32cbd307fb11279b8bb8778795dbad9e99d6..25d4dbd4ff464acce832bbf818f75fa72daf2fef 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import { connect } from 'react-redux';
+import { Alert } from 'sonar-ui-common/components/ui/Alert';
 import { translate } from 'sonar-ui-common/helpers/l10n';
 import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
+import { Store } from '../../../store/rootReducer';
 import './Login.css';
 import LoginForm from './LoginForm';
 import OAuthProviders from './OAuthProviders';
 
-interface Props {
+export interface LoginProps {
+  authorizationError?: boolean;
+  authenticationError?: boolean;
   identityProviders: T.IdentityProvider[];
   onSubmit: (login: string, password: string) => Promise<void>;
   returnTo: string;
 }
 
-export default function Login({ identityProviders, onSubmit, returnTo }: Props) {
+export function Login(props: LoginProps) {
+  const { authorizationError, authenticationError, identityProviders, returnTo } = props;
+  const displayError = authorizationError || authenticationError;
+
   return (
     <div className="login-page" id="login_form">
-      <h1 className="login-title text-center">{translate('login.login_to_sonarqube')}</h1>
+      <h1 className="login-title text-center huge-spacer-bottom">
+        {translate('login.login_to_sonarqube')}
+      </h1>
 
-      <GlobalMessagesContainer />
+      {displayError && (
+        <Alert className="huge-spacer-bottom" display="block" variant="error">
+          {translate('login.unauthorized_access_alert')}
+        </Alert>
+      )}
 
       {identityProviders.length > 0 && (
         <OAuthProviders identityProviders={identityProviders} returnTo={returnTo} />
       )}
 
-      <LoginForm collapsed={identityProviders.length > 0} onSubmit={onSubmit} returnTo={returnTo} />
+      <LoginForm
+        collapsed={identityProviders.length > 0}
+        onSubmit={props.onSubmit}
+        returnTo={returnTo}
+      />
+
+      <GlobalMessagesContainer />
     </div>
   );
 }
+
+const mapStateToProps = (state: Store) => ({
+  authorizationError: state.appState.authorizationError,
+  authenticationError: state.appState.authenticationError
+});
+
+export default connect(mapStateToProps)(Login);
index 556d930ede181b46141cc1ffa91817415dc4be9e..bdd88e5e5c0c229d2e60e8fb5a6de874dcd3d099 100644 (file)
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import Login from '../Login';
+import { Login, LoginProps } from '../Login';
 
-const identityProvider = {
-  backgroundColor: '#000',
-  iconPath: '/some/path',
-  key: 'foo',
-  name: 'foo'
-};
-
-it('logs in with form alone', () => {
-  const wrapper = shallow(<Login identityProviders={[]} onSubmit={jest.fn()} returnTo="" />);
-  expect(wrapper).toMatchSnapshot();
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot('with identity providers');
+  expect(shallowRender({ identityProviders: [] })).toMatchSnapshot(
+    'without any identity providers'
+  );
+  expect(shallowRender({ authorizationError: true })).toMatchSnapshot('with authorization error');
 });
 
-it('logs in with identity provider', () => {
-  const wrapper = shallow(
-    <Login identityProviders={[identityProvider]} onSubmit={jest.fn()} returnTo="" />
+function shallowRender(props: Partial<LoginProps> = {}) {
+  return shallow<LoginProps>(
+    <Login
+      identityProviders={[
+        {
+          backgroundColor: '#000',
+          iconPath: '/some/path',
+          key: 'foo',
+          name: 'foo'
+        }
+      ]}
+      onSubmit={jest.fn()}
+      returnTo=""
+      {...props}
+    />
   );
-  expect(wrapper).toMatchSnapshot();
-});
+}
index 9319b5a2187b1bcf852682008f25f42051c8a192..d09b2373fa9f889fc5849e7d00b3deea3fe969c3 100644 (file)
@@ -1,35 +1,54 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`logs in with form alone 1`] = `
+exports[`should render correctly: with authorization error 1`] = `
 <div
   className="login-page"
   id="login_form"
 >
   <h1
-    className="login-title text-center"
+    className="login-title text-center huge-spacer-bottom"
   >
     login.login_to_sonarqube
   </h1>
-  <Connect(GlobalMessages) />
+  <Alert
+    className="huge-spacer-bottom"
+    display="block"
+    variant="error"
+  >
+    login.unauthorized_access_alert
+  </Alert>
+  <OAuthProviders
+    identityProviders={
+      Array [
+        Object {
+          "backgroundColor": "#000",
+          "iconPath": "/some/path",
+          "key": "foo",
+          "name": "foo",
+        },
+      ]
+    }
+    returnTo=""
+  />
   <LoginForm
-    collapsed={false}
+    collapsed={true}
     onSubmit={[MockFunction]}
     returnTo=""
   />
+  <Connect(GlobalMessages) />
 </div>
 `;
 
-exports[`logs in with identity provider 1`] = `
+exports[`should render correctly: with identity providers 1`] = `
 <div
   className="login-page"
   id="login_form"
 >
   <h1
-    className="login-title text-center"
+    className="login-title text-center huge-spacer-bottom"
   >
     login.login_to_sonarqube
   </h1>
-  <Connect(GlobalMessages) />
   <OAuthProviders
     identityProviders={
       Array [
@@ -48,5 +67,25 @@ exports[`logs in with identity provider 1`] = `
     onSubmit={[MockFunction]}
     returnTo=""
   />
+  <Connect(GlobalMessages) />
+</div>
+`;
+
+exports[`should render correctly: without any identity providers 1`] = `
+<div
+  className="login-page"
+  id="login_form"
+>
+  <h1
+    className="login-title text-center huge-spacer-bottom"
+  >
+    login.login_to_sonarqube
+  </h1>
+  <LoginForm
+    collapsed={false}
+    onSubmit={[MockFunction]}
+    returnTo=""
+  />
+  <Connect(GlobalMessages) />
 </div>
 `;
index dd0cc38ae080fb00e8de8e52abd126e85a21e5cf..cbd6e21d99a1451d2cedde86941a29fa7941d97b 100644 (file)
@@ -1,7 +1,7 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`should render correctly 1`] = `
-<Login
+<Connect(Login)
   identityProviders={
     Array [
       Object {
index 87cccf03f55e5ec55048260e2a1fc6b289d967da..57394c8d3660f68d01c1d65d1df4cc27c485833f 100644 (file)
@@ -19,7 +19,6 @@
  */
 import { uniqueId } from 'lodash';
 import { Dispatch } from 'redux';
-import { requireAuthorization } from './appState';
 import { ActionType } from './utils/actions';
 
 enum MessageLevel {
@@ -48,8 +47,7 @@ export function closeAllGlobalMessages() {
 type Action =
   | ActionType<typeof addGlobalMessageActionCreator, 'ADD_GLOBAL_MESSAGE'>
   | ActionType<typeof closeGlobalMessage, 'CLOSE_GLOBAL_MESSAGE'>
-  | ActionType<typeof closeAllGlobalMessages, 'CLOSE_ALL_GLOBAL_MESSAGES'>
-  | ActionType<typeof requireAuthorization, 'REQUIRE_AUTHORIZATION'>;
+  | ActionType<typeof closeAllGlobalMessages, 'CLOSE_ALL_GLOBAL_MESSAGES'>;
 
 function addGlobalMessage(message: string, level: MessageLevel) {
   return (dispatch: Dispatch) => {
@@ -74,18 +72,6 @@ export default function(state: State = [], action: Action): State {
     case 'ADD_GLOBAL_MESSAGE':
       return [{ id: action.id, message: action.message, level: action.level }];
 
-    case 'REQUIRE_AUTHORIZATION':
-      // FIXME l10n
-      return [
-        {
-          id: uniqueId('global-message-'),
-          message:
-            'You are not authorized to access this page. ' +
-            'Please log in with more privileges and try again.',
-          level: MessageLevel.Error
-        }
-      ];
-
     case 'CLOSE_GLOBAL_MESSAGE':
       return state.filter(message => message.id !== action.id);