+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.
- */
-import * as React from 'react';
-import { FormattedMessage } from 'react-intl';
-import { Alert } from 'sonar-ui-common/components/ui/Alert';
-import { getTextColor } from 'sonar-ui-common/helpers/colors';
-import { getCookie } from 'sonar-ui-common/helpers/cookies';
-import { translate } from 'sonar-ui-common/helpers/l10n';
-import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
-import { getIdentityProviders } from '../../../api/users';
-import { colors } from '../../../app/theme';
-
-interface State {
- identityProviders: T.IdentityProvider[];
-}
-
-export default class EmailAlreadyExists extends React.PureComponent<{}, State> {
- mounted = false;
- state: State = { identityProviders: [] };
-
- componentDidMount() {
- this.mounted = true;
- this.fetchIdentityProviders();
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- fetchIdentityProviders = () => {
- getIdentityProviders().then(
- ({ identityProviders }) => {
- if (this.mounted) {
- this.setState({ identityProviders });
- }
- },
- () => {}
- );
- };
-
- getAuthError = (): {
- email?: string;
- login?: string;
- provider?: string;
- existingLogin?: string;
- existingProvider?: string;
- } => {
- const cookie = getCookie('AUTHENTICATION-ERROR');
- if (cookie) {
- return JSON.parse(decodeURIComponent(cookie));
- }
- return {};
- };
-
- renderIdentityProvier = (provider?: string, login?: string) => {
- const identityProvider = this.state.identityProviders.find(p => p.key === provider);
-
- return identityProvider ? (
- <div
- className="identity-provider"
- style={{
- backgroundColor: identityProvider.backgroundColor,
- color: getTextColor(identityProvider.backgroundColor, colors.secondFontColor)
- }}>
- <img
- alt={identityProvider.name}
- className="little-spacer-right"
- height="14"
- src={getBaseUrl() + identityProvider.iconPath}
- width="14"
- />
- {login}
- </div>
- ) : (
- <div>
- {provider !== 'sonarqube' && provider} {login}
- </div>
- );
- };
-
- render() {
- const authError = this.getAuthError();
- return (
- <div className="page-wrapper-simple" id="bd">
- <div className="page-simple" id="nonav">
- <div className="big-spacer-bottom js-existing-account">
- <p className="little-spacer-bottom">
- <FormattedMessage
- defaultMessage={translate('sessions.email_already_exists.1')}
- id="sessions.email_already_exists.1"
- values={{ email: <strong>{authError.email}</strong> }}
- />
- </p>
- {this.renderIdentityProvier(authError.existingProvider, authError.existingLogin)}
- </div>
-
- <div className="big-spacer-bottom js-new-account">
- <p className="little-spacer-bottom">{translate('sessions.email_already_exists.2')}</p>
- {this.renderIdentityProvier(authError.provider, authError.login)}
- </div>
-
- <Alert variant="warning">
- {translate('sessions.email_already_exists.3')}
- <ul className="list-styled">
- <li className="spacer-top">{translate('sessions.email_already_exists.4')}</li>
- <li className="spacer-top">{translate('sessions.email_already_exists.5')}</li>
- <li className="spacer-top">{translate('sessions.email_already_exists.6')}</li>
- </ul>
- </Alert>
-
- <div className="big-spacer-top text-right">
- <a
- className="button js-continue"
- href={`${getBaseUrl()}/sessions/init/${authError.provider}?allowEmailShift=true`}>
- {translate('continue')}
- </a>
- <a className="big-spacer-left js-cancel" href={getBaseUrl() + '/'}>
- {translate('cancel')}
- </a>
- </div>
- </div>
- </div>
- );
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
-import EmailAlreadyExists from '../EmailAlreadyExists';
-
-jest.mock('../../../../api/users', () => ({
- getIdentityProviders: () =>
- Promise.resolve({
- identityProviders: [
- {
- key: 'bitbucket',
- name: 'Bitbucket',
- iconPath: '/static/authbitbucket/bitbucket.svg',
- backgroundColor: '#0052cc'
- },
- {
- key: 'github',
- name: 'GitHub',
- iconPath: '/static/authgithub/github.svg',
- backgroundColor: '#444444'
- }
- ]
- })
-}));
-
-jest.mock('sonar-ui-common/helpers/cookies', () => ({
- getCookie: jest
- .fn()
- .mockReturnValue(
- '%7B%22email%22%3A%22mail%40example.com%22%2C%22login%22%3A%22foo%22%2C%22provider%22%3A%22github%22%2C%22existingLogin%22%3A%22bar%22%2C%22existingProvider%22%3A%22bitbucket%22%7D'
- )
-}));
-
-it('render', async () => {
- const wrapper = shallow(<EmailAlreadyExists />);
- (wrapper.instance() as EmailAlreadyExists).mounted = true;
- (wrapper.instance() as EmailAlreadyExists).fetchIdentityProviders();
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`render 1`] = `
-<div
- className="page-wrapper-simple"
- id="bd"
->
- <div
- className="page-simple"
- id="nonav"
- >
- <div
- className="big-spacer-bottom js-existing-account"
- >
- <p
- className="little-spacer-bottom"
- >
- <FormattedMessage
- defaultMessage="sessions.email_already_exists.1"
- id="sessions.email_already_exists.1"
- values={
- Object {
- "email": <strong>
- mail@example.com
- </strong>,
- }
- }
- />
- </p>
- <div
- className="identity-provider"
- style={
- Object {
- "backgroundColor": "#0052cc",
- "color": "#fff",
- }
- }
- >
- <img
- alt="Bitbucket"
- className="little-spacer-right"
- height="14"
- src="/static/authbitbucket/bitbucket.svg"
- width="14"
- />
- bar
- </div>
- </div>
- <div
- className="big-spacer-bottom js-new-account"
- >
- <p
- className="little-spacer-bottom"
- >
- sessions.email_already_exists.2
- </p>
- <div
- className="identity-provider"
- style={
- Object {
- "backgroundColor": "#444444",
- "color": "#fff",
- }
- }
- >
- <img
- alt="GitHub"
- className="little-spacer-right"
- height="14"
- src="/static/authgithub/github.svg"
- width="14"
- />
- foo
- </div>
- </div>
- <Alert
- variant="warning"
- >
- sessions.email_already_exists.3
- <ul
- className="list-styled"
- >
- <li
- className="spacer-top"
- >
- sessions.email_already_exists.4
- </li>
- <li
- className="spacer-top"
- >
- sessions.email_already_exists.5
- </li>
- <li
- className="spacer-top"
- >
- sessions.email_already_exists.6
- </li>
- </ul>
- </Alert>
- <div
- className="big-spacer-top text-right"
- >
- <a
- className="button js-continue"
- href="/sessions/init/github?allowEmailShift=true"
- >
- continue
- </a>
- <a
- className="big-spacer-left js-cancel"
- href="/"
- >
- cancel
- </a>
- </div>
- </div>
-</div>
-`;
{
path: 'unauthorized',
component: lazyLoadComponent(() => import('./components/Unauthorized'))
- },
- {
- path: 'email_already_exists',
- component: lazyLoadComponent(() => import('./components/EmailAlreadyExists'))
}
];
import org.sonar.api.server.authentication.BaseIdentityProvider;
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.db.user.UserDto;
-import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy;
import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.user.ThreadLocalUserSession;
import org.sonar.server.user.UserSessionFactory;
.setUserIdentity(userIdentity)
.setProvider(identityProvider)
.setSource(Source.external(identityProvider))
- .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
.build());
jwtHttpHandler.generateToken(userDto, request, response);
threadLocalUserSession.set(userSessionFactory.create(userDto));
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.user.UserDto;
-import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.authentication.event.AuthenticationException;
.setUserIdentity(userIdentityBuilder.build())
.setProvider(new ExternalIdentityProvider())
.setSource(realmEventSource(method))
- .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
.build());
}
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.user.UserDto;
import org.sonar.process.ProcessProperties;
-import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.authentication.event.AuthenticationException;
.setUserIdentity(userIdentityBuilder.build())
.setProvider(new SsoIdentityProvider())
.setSource(Source.sso())
- .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
.build());
}
import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
-import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException;
import static java.lang.String.format;
import static org.sonar.server.authentication.AuthenticationError.handleAuthenticationError;
import static org.sonar.server.authentication.AuthenticationError.handleError;
-import static org.sonar.server.authentication.AuthenticationRedirection.redirectTo;
import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
public class InitFilter extends AuthenticationFilter {
} else {
handleError(request, response, format("Unsupported IdentityProvider class: %s", provider.getClass()));
}
- } catch (EmailAlreadyExistsRedirectionException e) {
- oAuthOAuth2AuthenticationParameters.delete(request, response);
- e.addCookie(request, response);
- redirectTo(response, e.getPath(request.getContextPath()));
} catch (AuthenticationException e) {
oAuthOAuth2AuthenticationParameters.delete(request, response);
authenticationEvent.loginFailure(request, e);
Optional<String> getReturnTo(HttpServletRequest request);
- Optional<Boolean> getAllowEmailShift(HttpServletRequest request);
-
- Optional<Boolean> getAllowUpdateLogin(HttpServletRequest request);
-
void delete(HttpServletRequest request, HttpServletResponse response);
}
import static java.net.URLDecoder.decode;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Optional.empty;
-import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.sonar.server.authentication.AuthenticationRedirection.encodeMessage;
import static org.sonar.server.authentication.Cookies.findCookie;
import static org.sonar.server.authentication.Cookies.newCookieBuilder;
*/
private static final String RETURN_TO_PARAMETER = "return_to";
- /**
- * This parameter is used to allow the shift of email from an existing user to the authenticating user
- */
- private static final String ALLOW_EMAIL_SHIFT_PARAMETER = "allowEmailShift";
-
/**
* This parameter is used to allow the update of login
*/
@Override
public void init(HttpServletRequest request, HttpServletResponse response) {
String returnTo = request.getParameter(RETURN_TO_PARAMETER);
- String allowEmailShift = request.getParameter(ALLOW_EMAIL_SHIFT_PARAMETER);
Map<String, String> parameters = new HashMap<>();
Optional<String> sanitizeRedirectUrl = sanitizeRedirectUrl(returnTo);
sanitizeRedirectUrl.ifPresent(s -> parameters.put(RETURN_TO_PARAMETER, s));
- if (isNotBlank(allowEmailShift)) {
- parameters.put(ALLOW_EMAIL_SHIFT_PARAMETER, allowEmailShift);
- }
if (parameters.isEmpty()) {
return;
}
.flatMap(OAuth2AuthenticationParametersImpl::sanitizeRedirectUrl);
}
- @Override
- public Optional<Boolean> getAllowEmailShift(HttpServletRequest request) {
- Optional<String> parameter = getParameter(request, ALLOW_EMAIL_SHIFT_PARAMETER);
- return parameter.map(Boolean::parseBoolean);
- }
-
- @Override
- public Optional<Boolean> getAllowUpdateLogin(HttpServletRequest request) {
- Optional<String> parameter = getParameter(request, ALLOW_LOGIN_UPDATE_PARAMETER);
- return parameter.map(Boolean::parseBoolean);
- }
-
private static Optional<String> getParameter(HttpServletRequest request, String parameterKey) {
Optional<javax.servlet.http.Cookie> cookie = findCookie(AUTHENTICATION_COOKIE_NAME, request);
if (!cookie.isPresent()) {
import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
-import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException;
import org.sonar.server.user.ThreadLocalUserSession;
import static java.lang.String.format;
import static org.sonar.server.authentication.AuthenticationError.handleAuthenticationError;
import static org.sonar.server.authentication.AuthenticationError.handleError;
-import static org.sonar.server.authentication.AuthenticationRedirection.redirectTo;
import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
public class OAuth2CallbackFilter extends AuthenticationFilter {
} else {
handleError(request, response, format("Not an OAuth2IdentityProvider: %s", provider.getClass()));
}
- } catch (EmailAlreadyExistsRedirectionException e) {
- oauth2Parameters.delete(request, response);
- e.addCookie(request, response);
- redirectTo(response, e.getPath(request.getContextPath()));
} catch (AuthenticationException e) {
oauth2Parameters.delete(request, response);
authenticationEvent.loginFailure(request, e);
import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.db.user.UserDto;
-import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.user.ThreadLocalUserSession;
import org.sonar.server.user.UserSessionFactory;
@Override
public void authenticate(UserIdentity userIdentity, @Nullable Set<String> organizationAlmIds) {
- boolean allowEmailShift = oAuthParameters.getAllowEmailShift(request).orElse(false);
UserDto userDto = userRegistrar.register(
UserRegistration.builder()
.setUserIdentity(userIdentity)
.setProvider(identityProvider)
.setSource(AuthenticationEvent.Source.oauth2(identityProvider))
- .setExistingEmailStrategy(allowEmailShift ? ExistingEmailStrategy.ALLOW : ExistingEmailStrategy.WARN)
.setOrganizationAlmIds(organizationAlmIds)
.build());
jwtHttpHandler.generateToken(userDto, request, response);
package org.sonar.server.authentication;
import com.google.common.collect.Sets;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.function.Consumer;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
-
import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.utils.log.Logger;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
import org.sonar.db.user.UserGroupDto;
-import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
-import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException;
import org.sonar.server.user.ExternalIdentity;
import org.sonar.server.user.NewUser;
import org.sonar.server.user.UpdateUser;
userIdentity,
source,
String.format("Login '%s' is already used", userIdentity.getProviderLogin()),
- String.format("You can't sign up because login '%s' is already used by an existing user.", userIdentity.getProviderLogin())
- );
+ String.format("You can't sign up because login '%s' is already used by an existing user.", userIdentity.getProviderLogin()));
}
private static AuthenticationException authException(UserIdentity userIdentity, AuthenticationEvent.Source source, String message, String publicMessage) {
if (existingUser == null || existingUser.getUuid().equals(authenticatingUserUuid)) {
return Optional.empty();
}
- ExistingEmailStrategy existingEmailStrategy = authenticatorParameters.getExistingEmailStrategy();
- switch (existingEmailStrategy) {
- case ALLOW:
- existingUser.setEmail(null);
- dbClient.userDao().update(dbSession, existingUser);
- return Optional.of(existingUser);
- case WARN:
- throw new EmailAlreadyExistsRedirectionException(email, existingUser, authenticatorParameters.getUserIdentity(), authenticatorParameters.getProvider());
- case FORBID:
- throw generateExistingEmailError(authenticatorParameters, email);
- default:
- throw new IllegalStateException(format("Unknown strategy %s", existingEmailStrategy));
- }
+ throw generateExistingEmailError(authenticatorParameters, email);
}
private void syncGroups(DbSession dbSession, UserIdentity userIdentity, UserDto userDto) {
}
private static UserDto[] toArray(Optional<UserDto> userDto) {
- return userDto.map(u -> new UserDto[]{u}).orElse(new UserDto[]{});
+ return userDto.map(u -> new UserDto[] {u}).orElse(new UserDto[] {});
}
private static AuthenticationException generateExistingEmailError(UserRegistration authenticatorParameters, String email) {
.setSource(authenticatorParameters.getSource())
.setLogin(authenticatorParameters.getUserIdentity().getProviderLogin())
.setMessage(format("Email '%s' is already used", email))
- .setPublicMessage(format(
- "You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.",
- email))
+ .setPublicMessage(
+ "This account is already associated with another authentication method. "
+ + "Sign in using the current authentication method, "
+ + "or contact your administrator to transfer your account to a different authentication method.")
.build();
}
class UserRegistration {
- /**
- * Strategy to be executed when the email of the user is already used by another user
- */
- enum ExistingEmailStrategy {
- /**
- * Authentication is allowed, the email is moved from other user to current user
- */
- ALLOW,
- /**
- * Authentication process is stopped, the user is redirected to a page explaining that the email is already used
- */
- WARN,
- /**
- * Forbid authentication of the user
- */
- FORBID
- }
-
private final UserIdentity userIdentity;
private final IdentityProvider provider;
private final AuthenticationEvent.Source source;
- private final ExistingEmailStrategy existingEmailStrategy;
private final Set<String> organizationAlmIds;
UserRegistration(Builder builder) {
this.userIdentity = builder.userIdentity;
this.provider = builder.provider;
this.source = builder.source;
- this.existingEmailStrategy = builder.existingEmailStrategy;
this.organizationAlmIds = builder.organizationAlmIds;
}
return source;
}
- public ExistingEmailStrategy getExistingEmailStrategy() {
- return existingEmailStrategy;
- }
-
@CheckForNull
public Set<String> getOrganizationAlmIds() {
return organizationAlmIds;
private UserIdentity userIdentity;
private IdentityProvider provider;
private AuthenticationEvent.Source source;
- private ExistingEmailStrategy existingEmailStrategy;
private Set<String> organizationAlmIds;
public Builder setUserIdentity(UserIdentity userIdentity) {
return this;
}
- /**
- * Strategy to be executed when the email of the user is already used by another user
- */
- public Builder setExistingEmailStrategy(ExistingEmailStrategy existingEmailStrategy) {
- this.existingEmailStrategy = existingEmailStrategy;
- return this;
- }
-
/**
* List of ALM organization the user is member of.
* When set to null, it means that no organization membership synchronization should be done.
requireNonNull(userIdentity, "userIdentity must be set");
requireNonNull(provider, "identityProvider must be set");
requireNonNull(source, "Source must be set");
- requireNonNull(existingEmailStrategy, "existingEmailStrategy must be set ");
return new UserRegistration(this);
}
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.
- */
-package org.sonar.server.authentication.exception;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.sonar.api.server.authentication.IdentityProvider;
-import org.sonar.api.server.authentication.UserIdentity;
-import org.sonar.db.user.UserDto;
-
-import static org.sonar.server.authentication.AuthenticationError.addErrorCookie;
-
-/**
- * This exception is used to redirect the user to a page explaining him that his email is already used by another account,
- * and where he has the ability to authenticate by "steeling" this email.
- */
-public class EmailAlreadyExistsRedirectionException extends RedirectionException {
-
- private static final String PATH = "/sessions/email_already_exists";
- private static final String EMAIL_FIELD = "email";
- private static final String LOGIN_FIELD = "login";
- private static final String PROVIDER_FIELD = "provider";
- private static final String EXISTING_LOGIN_FIELD = "existingLogin";
- private static final String EXISTING_PROVIDER_FIELD = "existingProvider";
-
- private final String email;
- private final UserDto existingUser;
- private final UserIdentity userIdentity;
- private final IdentityProvider provider;
-
- public EmailAlreadyExistsRedirectionException(String email, UserDto existingUser, UserIdentity userIdentity, IdentityProvider provider) {
- this.email = email;
- this.existingUser = existingUser;
- this.userIdentity = userIdentity;
- this.provider = provider;
- }
-
- public void addCookie(HttpServletRequest request, HttpServletResponse response) {
- Gson gson = new GsonBuilder().create();
- String message = gson.toJson(ImmutableMap.of(
- EMAIL_FIELD, email,
- LOGIN_FIELD, userIdentity.getProviderLogin(),
- PROVIDER_FIELD, provider.getKey(),
- EXISTING_LOGIN_FIELD, existingUser.getExternalLogin(),
- EXISTING_PROVIDER_FIELD, existingUser.getExternalIdentityProvider()));
- addErrorCookie(request, response, message);
- }
-
- @Override
- public String getPath(String contextPath) {
- return contextPath + PATH;
- }
-}
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import static org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy.FORBID;
import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC_TOKEN;
import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException;
underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC);
assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
- assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getExistingEmailStrategy()).isEqualTo(FORBID);
assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo(LOGIN);
assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderId()).isNull();
assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo("name");
import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import org.sonar.api.server.authentication.UnauthorizedException;
-import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.db.user.UserDto;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
-import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import static org.sonar.db.user.UserTesting.newUserDto;
public class InitFilterTest {
verify(response).sendRedirect("/sonarqube/sessions/unauthorized");
}
- @Test
- public void redirect_contains_cookie_when_failing_because_of_EmailAlreadyExistException() throws Exception {
- UserDto existingUser = newUserDto().setEmail("john@email.com").setExternalLogin("john.bitbucket").setExternalIdentityProvider("bitbucket");
- FailWithEmailAlreadyExistException identityProvider = new FailWithEmailAlreadyExistException("failing", existingUser);
- when(request.getRequestURI()).thenReturn("/sessions/init/" + identityProvider.getKey());
- identityProviderRepository.addIdentityProvider(identityProvider);
-
- underTest.doFilter(request, response, chain);
-
- verify(response).sendRedirect("/sessions/email_already_exists");
- verify(auth2AuthenticationParameters).delete(eq(request), eq(response));
- verify(response).addCookie(cookieArgumentCaptor.capture());
- Cookie cookie = cookieArgumentCaptor.getValue();
- assertThat(cookie.getName()).isEqualTo("AUTHENTICATION-ERROR");
- assertThat(cookie.getValue()).contains("john%40email.com");
- assertThat(cookie.getPath()).isEqualTo("/");
- assertThat(cookie.isHttpOnly()).isFalse();
- assertThat(cookie.getMaxAge()).isEqualTo(300);
- assertThat(cookie.getSecure()).isFalse();
- }
-
@Test
public void redirect_when_failing_because_of_Exception() throws Exception {
IdentityProvider identityProvider = new FailWithIllegalStateException("failing");
}
}
- private static class FailWithEmailAlreadyExistException extends FakeBasicIdentityProvider {
-
- private final UserDto existingUser;
-
- public FailWithEmailAlreadyExistException(String key, UserDto existingUser) {
- super(key, true);
- this.existingUser = existingUser;
- }
-
- @Override
- public void init(Context context) {
- throw new EmailAlreadyExistsRedirectionException(existingUser.getEmail(), existingUser, UserIdentity.builder()
- .setProviderLogin("john.github")
- .setName(existingUser.getName())
- .setEmail(existingUser.getEmail())
- .build(), this);
- }
- }
-
private static class UnsupportedIdentityProvider implements IdentityProvider {
private final String unsupportedKey;
@Test
public void get_return_to_parameter() {
- when(request.getCookies()).thenReturn(new Cookie[]{new Cookie(AUTHENTICATION_COOKIE_NAME, "{\"return_to\":\"/settings\"}")});
+ when(request.getCookies()).thenReturn(new Cookie[] {new Cookie(AUTHENTICATION_COOKIE_NAME, "{\"return_to\":\"/settings\"}")});
Optional<String> redirection = underTest.getReturnTo(request);
@Test
public void get_return_to_is_empty_when_no_cookie() {
- when(request.getCookies()).thenReturn(new Cookie[]{});
+ when(request.getCookies()).thenReturn(new Cookie[] {});
Optional<String> redirection = underTest.getReturnTo(request);
@Test
public void get_return_to_is_empty_when_no_value() {
- when(request.getCookies()).thenReturn(new Cookie[]{new Cookie(AUTHENTICATION_COOKIE_NAME, "{}")});
+ when(request.getCookies()).thenReturn(new Cookie[] {new Cookie(AUTHENTICATION_COOKIE_NAME, "{}")});
Optional<String> redirection = underTest.getReturnTo(request);
assertThat(redirection).isEmpty();
}
- @Test
- public void get_allowEmailShift_parameter() {
- when(request.getCookies()).thenReturn(new Cookie[]{new Cookie(AUTHENTICATION_COOKIE_NAME, "{\"allowEmailShift\":\"true\"}")});
-
- Optional<Boolean> allowEmailShift = underTest.getAllowEmailShift(request);
-
- assertThat(allowEmailShift).isNotEmpty();
- assertThat(allowEmailShift.get()).isTrue();
- }
-
- @Test
- public void get_allowEmailShift_is_empty_when_no_cookie() {
- when(request.getCookies()).thenReturn(new Cookie[]{});
-
- Optional<Boolean> allowEmailShift = underTest.getAllowEmailShift(request);
-
- assertThat(allowEmailShift).isEmpty();
- }
-
- @Test
- public void get_allowEmailShift_is_empty_when_no_value() {
- when(request.getCookies()).thenReturn(new Cookie[]{new Cookie(AUTHENTICATION_COOKIE_NAME, "{}")});
-
- Optional<Boolean> allowEmailShift = underTest.getAllowEmailShift(request);
-
- assertThat(allowEmailShift).isEmpty();
- }
-
- @Test
- public void getAllowUpdateLogin_is_empty_when_no_cookie() {
- when(request.getCookies()).thenReturn(new Cookie[]{});
-
- Optional<Boolean> allowLoginUpdate = underTest.getAllowUpdateLogin(request);
-
- assertThat(allowLoginUpdate).isEmpty();
- }
-
- @Test
- public void getAllowUpdateLogin_is_empty_when_no_value() {
- when(request.getCookies()).thenReturn(new Cookie[]{new Cookie(AUTHENTICATION_COOKIE_NAME, "{}")});
-
- Optional<Boolean> allowLoginUpdate = underTest.getAllowUpdateLogin(request);
-
- assertThat(allowLoginUpdate).isEmpty();
- }
-
@Test
public void delete() {
- when(request.getCookies()).thenReturn(new Cookie[]{new Cookie(AUTHENTICATION_COOKIE_NAME, "{\"return_to\":\"/settings\"}")});
+ when(request.getCookies()).thenReturn(new Cookie[] {new Cookie(AUTHENTICATION_COOKIE_NAME, "{\"return_to\":\"/settings\"}")});
underTest.delete(request, response);
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.db.user.UserDto;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
-import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException;
import org.sonar.server.user.ThreadLocalUserSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import static org.sonar.db.user.UserTesting.newUserDto;
import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
public class OAuth2CallbackFilterTest {
verify(response).sendRedirect("/sonarqube/sessions/unauthorized");
}
- @Test
- public void redirect_when_failing_because_of_EmailAlreadyExistException() throws Exception {
- UserDto existingUser = newUserDto().setEmail("john@email.com").setExternalLogin("john.bitbucket").setExternalIdentityProvider("bitbucket");
- FailWithEmailAlreadyExistException identityProvider = new FailWithEmailAlreadyExistException(existingUser);
- when(request.getRequestURI()).thenReturn("/oauth2/callback/" + identityProvider.getKey());
- identityProviderRepository.addIdentityProvider(identityProvider);
-
- underTest.doFilter(request, response, chain);
-
- verify(response).sendRedirect("/sessions/email_already_exists");
- verify(oAuthRedirection).delete(eq(request), eq(response));
- verify(response).addCookie(cookieArgumentCaptor.capture());
- Cookie cookie = cookieArgumentCaptor.getValue();
- assertThat(cookie.getName()).isEqualTo("AUTHENTICATION-ERROR");
- assertThat(cookie.getValue()).contains("john%40email.com");
- assertThat(cookie.getPath()).isEqualTo("/");
- assertThat(cookie.isHttpOnly()).isFalse();
- assertThat(cookie.getMaxAge()).isEqualTo(300);
- assertThat(cookie.getSecure()).isFalse();
- }
-
@Test
public void fail_when_no_oauth2_provider_provided() throws Exception {
when(request.getRequestURI()).thenReturn("/oauth2/callback");
}
}
- private static class FailWithEmailAlreadyExistException extends FailingIdentityProvider {
-
- private final UserDto existingUser;
-
- public FailWithEmailAlreadyExistException(UserDto existingUser) {
- this.existingUser = existingUser;
- }
-
- @Override
- public void callback(CallbackContext context) {
- throw new EmailAlreadyExistsRedirectionException(existingUser.getEmail(), existingUser, UserIdentity.builder()
- .setProviderLogin("john.github")
- .setName(existingUser.getName())
- .setEmail(existingUser.getEmail())
- .build(), this);
- }
- }
-
private static abstract class FailingIdentityProvider extends TestIdentityProvider implements OAuth2IdentityProvider {
FailingIdentityProvider() {
this.setKey("failing");
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.db.user.UserDto;
import org.sonar.server.authentication.OAuth2ContextFactory.OAuthContextImpl;
-import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy;
import org.sonar.server.user.TestUserSessionFactory;
import org.sonar.server.user.ThreadLocalUserSession;
import org.sonar.server.user.UserSession;
assertThat(userArgumentCaptor.getValue().getExternalIdentityProvider()).isEqualTo(PROVIDER_KEY);
}
- @Test
- public void authenticate_with_allow_email_shift() {
- when(oAuthParameters.getAllowEmailShift(request)).thenReturn(Optional.of(true));
- OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
-
- callback.authenticate(USER_IDENTITY);
-
- assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
- assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getExistingEmailStrategy()).isEqualTo(ExistingEmailStrategy.ALLOW);
- }
-
- @Test
- public void authenticate_without_email_shift() {
- when(oAuthParameters.getAllowEmailShift(request)).thenReturn(Optional.of(false));
- OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
-
- callback.authenticate(USER_IDENTITY);
-
- assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
- assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getExistingEmailStrategy()).isEqualTo(ExistingEmailStrategy.WARN);
- }
-
@Test
public void authenticate_with_organization_alm_ids() {
OAuthContextImpl callback = (OAuthContextImpl) newCallbackContext();
import org.sonar.db.DbTester;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
-import org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.authentication.event.AuthenticationException;
-import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException;
import org.sonar.server.es.EsTester;
import org.sonar.server.user.NewUserNotifier;
import org.sonar.server.user.UserUpdater;
import static org.mockito.Mockito.mock;
import static org.sonar.db.user.UserTesting.newUserDto;
import static org.sonar.process.ProcessProperties.Property.ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS;
-import static org.sonar.server.authentication.UserRegistration.ExistingEmailStrategy.FORBID;
import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException;
.setUserIdentity(USER_IDENTITY)
.setProvider(sqIdentityProvider)
.setSource(Source.realm(BASIC, sqIdentityProvider.getName()))
- .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
.build());
UserDto user = db.users().selectUserByLogin(createdUser.getLogin()).get();
.setEnabled(true)
.setAllowsUsersToSignUp(true))
.setSource(Source.local(BASIC))
- .setExistingEmailStrategy(FORBID)
.build();
UserDto newUser = underTest.register(registration);
}
@Test
- public void authenticate_new_user_update_existing_user_email_when_strategy_is_ALLOW() {
- UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
- UserIdentity newUser = UserIdentity.builder()
- .setProviderLogin("johndoo")
- .setName(existingUser.getName())
- .setEmail(existingUser.getEmail())
- .build();
-
- UserDto user = underTest.register(UserRegistration.builder()
- .setUserIdentity(newUser)
- .setProvider(IDENTITY_PROVIDER)
- .setSource(Source.local(BASIC))
- .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW)
- .build());
-
- UserDto newUserReloaded = db.users().selectUserByLogin(user.getLogin()).get();
- assertThat(newUserReloaded.getEmail()).isEqualTo(existingUser.getEmail());
- UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get();
- assertThat(existingUserReloaded.getEmail()).isNull();
- }
-
- @Test
- public void authenticate_new_user_throws_EmailAlreadyExistException_when_email_already_exists_and_strategy_is_WARN() {
- UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
- UserIdentity newUser = UserIdentity.builder()
- .setProviderLogin("johndoo")
- .setName(existingUser.getName())
- .setEmail(existingUser.getEmail())
- .build();
-
- expectedException.expect(EmailAlreadyExistsRedirectionException.class);
-
- underTest.register(UserRegistration.builder()
- .setUserIdentity(newUser)
- .setProvider(IDENTITY_PROVIDER)
- .setSource(Source.local(BASIC))
- .setExistingEmailStrategy(ExistingEmailStrategy.WARN)
- .build());
- }
-
- @Test
- public void authenticate_new_user_throws_AuthenticationException_when_when_email_already_exists_and_strategy_is_FORBID() {
+ public void authenticate_new_user_throws_AuthenticationException_when_when_email_already_exists() {
db.users().insertUser(u -> u.setEmail("john@email.com"));
Source source = Source.local(BASIC);
expectedException.expect(authenticationException().from(source)
.withLogin(USER_IDENTITY.getProviderLogin())
- .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " +
- "This means that you probably already registered with another account."));
+ .andPublicMessage("This account is already associated with another authentication method."
+ + " Sign in using the current authentication method,"
+ + " or contact your administrator to transfer your account to a different authentication method."));
expectedException.expectMessage("Email 'john@email.com' is already used");
underTest.register(newUserRegistration());
expectedException.expect(authenticationException().from(source)
.withLogin(USER_IDENTITY.getProviderLogin())
- .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " +
- "This means that you probably already registered with another account."));
+ .andPublicMessage("This account is already associated with another authentication method."
+ + " Sign in using the current authentication method,"
+ + " or contact your administrator to transfer your account to a different authentication method."));
expectedException.expectMessage("Email 'john@email.com' is already used");
underTest.register(newUserRegistration(source));
.setUserIdentity(USER_IDENTITY)
.setProvider(identityProvider)
.setSource(source)
- .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
.build());
}
.setName("name of gitlab")
.setEnabled(true))
.setSource(Source.local(BASIC))
- .setExistingEmailStrategy(FORBID)
.build();
assertThatThrownBy(() -> underTest.register(registration))
}
@Test
- public void authenticate_existing_user_when_email_already_exists_and_strategy_is_ALLOW() {
- UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
- UserDto currentUser = db.users().insertUser(u -> u.setExternalId("id").setExternalLogin("login").setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()).setEmail(null));
-
- UserIdentity userIdentity = UserIdentity.builder()
- .setProviderLogin(currentUser.getExternalLogin())
- .setProviderId(currentUser.getExternalId())
- .setName("John")
- .setEmail("john@email.com")
- .build();
-
- currentUser = underTest.register(UserRegistration.builder()
- .setUserIdentity(userIdentity)
- .setProvider(IDENTITY_PROVIDER)
- .setSource(Source.local(BASIC))
- .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW)
- .build());
-
- UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get();
- assertThat(existingUserReloaded.getEmail()).isNull();
-
- UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get();
- assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com");
-
- }
-
- @Test
- public void authenticating_existing_user_throws_EmailAlreadyExistException_when_email_already_exists_and_strategy_is_WARN() {
- UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
- UserDto currentUser = db.users().insertUser(u -> u.setEmail(null));
- UserIdentity userIdentity = UserIdentity.builder()
- .setProviderLogin("johndoo")
- .setName("John")
- .setEmail("john@email.com")
- .build();
-
- expectedException.expect(EmailAlreadyExistsRedirectionException.class);
-
- underTest.register(UserRegistration.builder()
- .setUserIdentity(userIdentity)
- .setProvider(IDENTITY_PROVIDER)
- .setSource(Source.local(BASIC))
- .setExistingEmailStrategy(ExistingEmailStrategy.WARN)
- .build());
- }
-
- @Test
- public void authenticating_existing_user_throws_AuthenticationException_when_email_already_exists_and_strategy_is_FORBID() {
+ public void authenticating_existing_user_throws_AuthenticationException_when_email_already_exists() {
UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
UserDto currentUser = db.users().insertUser(u -> u.setEmail(null));
UserIdentity userIdentity = UserIdentity.builder()
Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName());
expectedException.expect(authenticationException().from(source)
.withLogin(userIdentity.getProviderLogin())
- .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " +
- "This means that you probably already registered with another account."));
+ .andPublicMessage("This account is already associated with another authentication method."
+ + " Sign in using the current authentication method,"
+ + " or contact your administrator to transfer your account to a different authentication method."));
expectedException.expectMessage("Email 'john@email.com' is already used");
underTest.register(newUserRegistration(userIdentity, source));
}
@Test
- public void authenticate_existing_user_succeeds_when_email_has_not_changed_and_strategy_is_FORBID() {
+ public void authenticate_existing_user_succeeds_when_email_has_not_changed() {
UserDto currentUser = db.users().insertUser(u -> u.setEmail("john@email.com")
.setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
UserIdentity userIdentity = UserIdentity.builder()
.setUserIdentity(userIdentity)
.setProvider(IDENTITY_PROVIDER)
.setSource(source)
- .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
.build();
}
#------------------------------------------------------------------------------
sessions.log_in=Log in
-sessions.email_already_exists.1=The email address {email} is already associated to this user account:
-sessions.email_already_exists.2=By clicking on "Continue" you will associate this email address to another user account:
-sessions.email_already_exists.3=This means the following:
-sessions.email_already_exists.4=Your email address will be erased from the first account.
-sessions.email_already_exists.5=You will no longer receive email notifications from this account.
-sessions.email_already_exists.6=Issues won't be automatically assigned to this account anymore.
-
#------------------------------------------------------------------------------
#
# HOTSPOTS