From 6ee2cedd71f83f65b18e5d3bc3d029e687443ce7 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Tue, 13 Jun 2017 05:14:16 -0700 Subject: [PATCH] SONAR-9357 Hide default login form if there are other ways to login (#2163) --- it/it-tests/src/test/java/it/ui/UiTest.java | 12 +- .../it/user/BaseIdentityProviderTest.java | 12 +- .../it/user/OAuth2IdentityProviderTest.java | 25 +- .../components/nav/global/GlobalNavUser.js | 26 +- .../src/main/js/app/utils/startReactApp.js | 3 +- .../components/AboutAppForSonarQubeDotCom.js | 15 +- .../apps/about/sonarqube-dot-com-styles.css | 32 +- .../js/apps/sessions/components/LoginForm.js | 130 +++++--- .../components/__tests__/LoginForm-test.js | 60 ++++ .../__snapshots__/LoginForm-test.js.snap | 285 ++++++++++++++++++ .../src/main/js/apps/sessions/routes.js | 38 ++- .../src/main/less/components/navbar.less | 5 + .../sonar-web/src/main/less/pages/login.less | 8 +- .../resources/org/sonar/l10n/core.properties | 3 + 14 files changed, 536 insertions(+), 118 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginForm-test.js create mode 100644 server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.js.snap diff --git a/it/it-tests/src/test/java/it/ui/UiTest.java b/it/it-tests/src/test/java/it/ui/UiTest.java index 9c002a029a1..6321cee2e85 100644 --- a/it/it-tests/src/test/java/it/ui/UiTest.java +++ b/it/it-tests/src/test/java/it/ui/UiTest.java @@ -22,14 +22,14 @@ package it.ui; import com.sonar.orchestrator.Orchestrator; import com.sonar.orchestrator.build.SonarScanner; import it.Category4Suite; -import it.user.ForceAuthenticationTest; import java.util.Map; +import org.junit.After; +import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.WsResponse; -import org.sonarqube.ws.client.setting.SetRequest; import pageobjects.Navigation; import util.ItUtils; @@ -40,6 +40,7 @@ import static com.codeborne.selenide.Selenide.$; import static com.codeborne.selenide.WebDriverRunner.url; import static org.assertj.core.api.Assertions.assertThat; import static util.ItUtils.projectDir; +import static util.ItUtils.resetSettings; import static util.ItUtils.setServerProperty; public class UiTest { @@ -50,6 +51,12 @@ public class UiTest { @Rule public Navigation nav = Navigation.get(ORCHESTRATOR); + @Before + @After + public void resetData() throws Exception { + resetSettings(ORCHESTRATOR, null, "sonar.forceAuthentication"); + } + @Test public void footer_contains_information() { nav.getFooter() @@ -81,7 +88,6 @@ public class UiTest { nav.getFooter() .shouldNot(hasText("About")) .shouldNot(hasText("Web API")); - setServerProperty(ORCHESTRATOR, "sonar.forceAuthentication", null); } @Test diff --git a/it/it-tests/src/test/java/it/user/BaseIdentityProviderTest.java b/it/it-tests/src/test/java/it/user/BaseIdentityProviderTest.java index 36dc82ec7a9..69213444b2a 100644 --- a/it/it-tests/src/test/java/it/user/BaseIdentityProviderTest.java +++ b/it/it-tests/src/test/java/it/user/BaseIdentityProviderTest.java @@ -26,6 +26,7 @@ import it.Category4Suite; import java.io.File; import org.apache.commons.io.FileUtils; import org.junit.After; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; @@ -77,12 +78,17 @@ public class BaseIdentityProviderTest { adminWsClient = newAdminWsClient(ORCHESTRATOR); } + @Before @After - public void cleanUpUsersAndGroupsAndProperties() throws Exception { + public void resetData() throws Exception { userRule.resetUsers(); userRule.removeGroups(GROUP1, GROUP2, GROUP3); - resetSettings(ORCHESTRATOR, null, "sonar.auth.fake-base-id-provider.enabled", "sonar.auth.fake-base-id-provider.user", - "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", "sonar.auth.fake-base-id-provider.enabledGroupsSync", "sonar.auth.fake-base-id-provider.groups", + resetSettings(ORCHESTRATOR, null, + "sonar.auth.fake-base-id-provider.enabled", + "sonar.auth.fake-base-id-provider.user", + "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", + "sonar.auth.fake-base-id-provider.enabledGroupsSync", + "sonar.auth.fake-base-id-provider.groups", "sonar.auth.fake-base-id-provider.allowsUsersToSignUp"); } diff --git a/it/it-tests/src/test/java/it/user/OAuth2IdentityProviderTest.java b/it/it-tests/src/test/java/it/user/OAuth2IdentityProviderTest.java index fa91d6cfefb..d202a6b8402 100644 --- a/it/it-tests/src/test/java/it/user/OAuth2IdentityProviderTest.java +++ b/it/it-tests/src/test/java/it/user/OAuth2IdentityProviderTest.java @@ -70,34 +70,35 @@ public class OAuth2IdentityProviderTest { String fakeServerAuthProviderUrl; @BeforeClass - public static void resetData() { + public static void initData() { ORCHESTRATOR.resetData(); adminWsClient = newAdminWsClient(ORCHESTRATOR); } - @After - public void resetUsers() throws Exception { - userRule.resetUsers(); - } - @Before public void setUp() throws Exception { fakeServerAuthProvider = new MockWebServer(); fakeServerAuthProvider.start(); fakeServerAuthProviderUrl = fakeServerAuthProvider.url("").url().toString(); - userRule.resetUsers(); - resetSettings(ORCHESTRATOR, null, "sonar.auth.fake-oauth2-id-provider.enabled", - "sonar.auth.fake-oauth2-id-provider.url", - "sonar.auth.fake-oauth2-id-provider.user", - "sonar.auth.fake-oauth2-id-provider.throwUnauthorizedMessage", - "sonar.auth.fake-oauth2-id-provider.allowsUsersToSignUp"); + resetData(); } @After public void tearDown() throws Exception { + resetData(); fakeServerAuthProvider.shutdown(); } + private void resetData(){ + userRule.resetUsers(); + resetSettings(ORCHESTRATOR, null, + "sonar.auth.fake-oauth2-id-provider.enabled", + "sonar.auth.fake-oauth2-id-provider.url", + "sonar.auth.fake-oauth2-id-provider.user", + "sonar.auth.fake-oauth2-id-provider.throwUnauthorizedMessage", + "sonar.auth.fake-oauth2-id-provider.allowsUsersToSignUp"); + } + @Test public void create_new_user_when_authenticate() throws Exception { simulateRedirectionToCallback(); diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js index 707e372a8ea..a4e39e09398 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js @@ -40,7 +40,8 @@ type Props = { fetchMyOrganizations: () => Promise<*>, location: Object, organizations: Array<{ key: string, name: string }>, - router: { push: string => void } + router: { push: string => void }, + sonarCloud: boolean }; type State = { @@ -156,11 +157,24 @@ export default class GlobalNavUser extends React.PureComponent { } renderAnonymous() { - return ( -
  • - {translate('layout.login')} -
  • - ); + return this.props.sonarCloud + ?
  • + + GitHub + {translate('layout.login')} + +
  • + :
  • + + {translate('layout.login')} + +
  • ; } render() { diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js index f0793da6798..1ffac0eb636 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.js +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js @@ -125,6 +125,7 @@ const startReactApp = () => { + @@ -138,7 +139,7 @@ const startReactApp = () => { - {sessionsRoutes} + diff --git a/server/sonar-web/src/main/js/apps/about/components/AboutAppForSonarQubeDotCom.js b/server/sonar-web/src/main/js/apps/about/components/AboutAppForSonarQubeDotCom.js index 91b533dd307..6072a5afb54 100644 --- a/server/sonar-web/src/main/js/apps/about/components/AboutAppForSonarQubeDotCom.js +++ b/server/sonar-web/src/main/js/apps/about/components/AboutAppForSonarQubeDotCom.js @@ -19,7 +19,6 @@ */ // @flow import React from 'react'; -import { Link } from 'react-router'; import AboutProjects from './AboutProjects'; import EntryIssueTypesForSonarQubeDotCom from './EntryIssueTypesForSonarQubeDotCom'; import AboutRulesForSonarQubeDotCom from './AboutRulesForSonarQubeDotCom'; @@ -29,7 +28,6 @@ import AboutQualityGates from './AboutQualityGates'; import AboutLeakPeriod from './AboutLeakPeriod'; import AboutStandards from './AboutStandards'; import AboutScanners from './AboutScanners'; -import { translate } from '../../../helpers/l10n'; import '../sonarqube-dot-com-styles.css'; type Props = { @@ -57,16 +55,11 @@ export default function AboutAppForSonarQubeDotCom(props: Props) {

    Continuous Code Quality
    as a Service

    - - Get Started - {!props.currentUser.isLoggedIn && - - {translate('layout.login')} - } + + GitHub + Connect With GitHub to Get Started + }
    diff --git a/server/sonar-web/src/main/js/apps/about/sonarqube-dot-com-styles.css b/server/sonar-web/src/main/js/apps/about/sonarqube-dot-com-styles.css index 693e4ee3908..1895646e681 100644 --- a/server/sonar-web/src/main/js/apps/about/sonarqube-dot-com-styles.css +++ b/server/sonar-web/src/main/js/apps/about/sonarqube-dot-com-styles.css @@ -31,35 +31,29 @@ font-weight: 300; } -.sqcom-about-page-intro > .button { +.sonarcloud-about-github-button { + display: inline-block; height: 44px; - line-height: 42px; + line-height: 46px; padding-left: 20px; padding-right: 20px; - border-color: #fff; + border: none; border-radius: 3px; - color: #fff; - font-size: 16px; + background-color: #444; + color: #fff !important; + font-size: 15px; font-weight: 500; text-transform: uppercase; - transition: none; -} - -.sqcom-about-page-intro > .button:hover { - background-color: #fff; - color: #4b9fd5; } -.sqcom-about-page-intro > .button-active { - border-color: #b0eb41; - background-color: #b0eb41; - color: #225463; +.sonarcloud-about-github-button:hover, +.sonarcloud-about-github-button:focus { + background-color: #333; } -.sqcom-about-page-intro > .button-active:hover { - border-color: #91d315; - background-color: #91d315; - color: #225463; +.sonarcloud-about-github-button img { + margin-top: 12px; + margin-right: 10px; } .sqcom-about-page-instance { diff --git a/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.js b/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.js index 06abd2f82c9..3ac61372fdf 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.js +++ b/server/sonar-web/src/main/js/apps/sessions/components/LoginForm.js @@ -22,26 +22,49 @@ import React from 'react'; import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; import { translate } from '../../../helpers/l10n'; +type Props = { + identityProviders: Array<{ + backgroundColor: string, + iconPath: string, + key: string, + name: string + }>, + onSubmit: (string, string) => void +}; + +type State = { + collapsed: boolean, + login: string, + password: string +}; + export default class LoginForm extends React.PureComponent { - static propTypes = { - identityProviders: React.PropTypes.array.isRequired, - onSubmit: React.PropTypes.func.isRequired - }; + props: Props; + state: State; - state = { - login: '', - password: '' - }; + constructor(props: Props) { + super(props); + this.state = { + collapsed: props.identityProviders.length > 0, + login: '', + password: '' + }; + } - handleSubmit = (e: Object) => { - e.preventDefault(); + handleSubmit = (event: Event) => { + event.preventDefault(); this.props.onSubmit(this.state.login, this.state.password); }; + handleMoreOptionsClick = (event: Event) => { + event.preventDefault(); + this.setState({ collapsed: false }); + }; + render() { return ( -
    -

    Log In to SonarQube

    +
    +

    {translate('login.login_to_sonarqube')}

    {this.props.identityProviders.length > 0 &&
    @@ -65,46 +88,55 @@ export default class LoginForm extends React.PureComponent {
    } -
    - + {this.state.collapsed + ? + : + -
    - - this.setState({ login: e.target.value })} - /> -
    +
    + + this.setState({ login: e.target.value })} + /> +
    -
    - - this.setState({ password: e.target.value })} - /> -
    +
    + + this.setState({ password: e.target.value })} + /> +
    -
    -
    - - {translate('cancel')} -
    -
    - +
    +
    + + {translate('cancel')} +
    +
    + }
    ); } diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginForm-test.js b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginForm-test.js new file mode 100644 index 00000000000..6eea8123a99 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginForm-test.js @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +// @flow +import React from 'react'; +import { shallow } from 'enzyme'; +import LoginForm from '../LoginForm'; +import { change, click, submit } from '../../../../helpers/testUtils'; + +const identityProvider = { + backgroundColor: '#000', + iconPath: '/some/path', + key: 'foo', + name: 'foo' +}; + +it('logs in with simple credentials', () => { + const onSubmit = jest.fn(); + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + + change(wrapper.find('#login'), 'admin'); + change(wrapper.find('#password'), 'admin'); + submit(wrapper.find('form')); + + expect(onSubmit).toBeCalledWith('admin', 'admin'); +}); + +it('logs in with identity provider', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); +}); + +it('expands more options', () => { + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + + click(wrapper.find('.js-more-options')); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.js.snap b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.js.snap new file mode 100644 index 00000000000..58c919aa1e8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.js.snap @@ -0,0 +1,285 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`expands more options 1`] = ` +
    +

    + login.login_to_sonarqube +

    +
    + +
    + +
    +`; + +exports[`expands more options 2`] = ` +
    +

    + login.login_to_sonarqube +

    +
    + +
    +
    + +
    + + +
    +
    + + +
    +
    +
    + + + cancel + +
    +
    + +
    +`; + +exports[`logs in with identity provider 1`] = ` +
    +

    + login.login_to_sonarqube +

    +
    + +
    + +
    +`; + +exports[`logs in with simple credentials 1`] = ` +
    +

    + login.login_to_sonarqube +

    +
    + +
    + + +
    +
    + + +
    +
    +
    + + + cancel + +
    +
    + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/sessions/routes.js b/server/sonar-web/src/main/js/apps/sessions/routes.js index 5909cbb53fd..957fb2a7207 100644 --- a/server/sonar-web/src/main/js/apps/sessions/routes.js +++ b/server/sonar-web/src/main/js/apps/sessions/routes.js @@ -17,15 +17,31 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import { Route, Redirect } from 'react-router'; -import LoginFormContainer from './components/LoginFormContainer'; -import Logout from './components/Logout'; -import Unauthorized from './components/Unauthorized'; - -export default [ - , - , - , - +const routes = [ + { + path: 'new', + getComponent(_, callback) { + require.ensure([], require => { + callback(null, require('./components/LoginFormContainer').default); + }); + } + }, + { + path: 'logout', + getComponent(_, callback) { + require.ensure([], require => { + callback(null, require('./components/Logout').default); + }); + } + }, + { + path: 'unauthorized', + getComponent(_, callback) { + require.ensure([], require => { + callback(null, require('./components/Unauthorized').default); + }); + } + } ]; + +export default routes; diff --git a/server/sonar-web/src/main/less/components/navbar.less b/server/sonar-web/src/main/less/components/navbar.less index fd7ac938e48..303d4b3cd78 100644 --- a/server/sonar-web/src/main/less/components/navbar.less +++ b/server/sonar-web/src/main/less/components/navbar.less @@ -267,6 +267,11 @@ } } +.navbar-global-login-github { + margin-top: 3px; + margin-right: 4px; +} + .navbar-context { position: static; diff --git a/server/sonar-web/src/main/less/pages/login.less b/server/sonar-web/src/main/less/pages/login.less index ce54c81cf7c..991fa4383b3 100644 --- a/server/sonar-web/src/main/less/pages/login.less +++ b/server/sonar-web/src/main/less/pages/login.less @@ -70,9 +70,6 @@ } .oauth-providers { - margin-bottom: 30px; - border-bottom: 1px solid @barBorderColor; - & > ul { display: flex; justify-content: space-around; @@ -106,3 +103,8 @@ } } } + +.oauth-providers + form { + padding-top: 30px; + border-top: 1px solid @barBorderColor; +} 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 b64d2721158..ee0c39066aa 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1841,6 +1841,9 @@ user.scm_account_already_used=The scm account '{0}' is already used by user(s) : user.login_or_email_used_as_scm_account=Login and email are automatically considered as SCM accounts user.password_cant_be_changed_on_external_auth=Password cannot be changed when external authentication is used +login.login_to_sonarqube=Log In to SonarQube +login.more_options=More options + #------------------------------------------------------------------------------ # # USERS PAGE -- 2.39.5