Browse Source

SONAR-7217 Fail to authenticate user with existing email

tags/5.4-M10
Julien Lancelot 8 years ago
parent
commit
3219b99f2e
17 changed files with 166 additions and 4 deletions
  1. 6
    1
      server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationError.java
  2. 35
    0
      server/sonar-server/src/main/java/org/sonar/server/authentication/EmailAlreadyExistsException.java
  3. 3
    0
      server/sonar-server/src/main/java/org/sonar/server/authentication/InitFilter.java
  4. 2
    0
      server/sonar-server/src/main/java/org/sonar/server/authentication/NotAllowUserToSignUpException.java
  5. 3
    0
      server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackFilter.java
  6. 6
    0
      server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticator.java
  7. 51
    0
      server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java
  8. 10
    0
      server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java
  9. 10
    0
      server/sonar-web/src/main/webapp/WEB-INF/app/views/sessions/email_already_exists.erb
  10. 1
    1
      server/sonar-web/src/main/webapp/WEB-INF/app/views/sessions/not_allowed_to_sign_up.html.erb
  11. 7
    0
      sonar-db/src/main/java/org/sonar/db/user/UserDao.java
  12. 2
    0
      sonar-db/src/main/java/org/sonar/db/user/UserMapper.java
  13. 6
    0
      sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml
  14. 9
    0
      sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java
  15. 8
    0
      sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/exists_by_email.xml
  16. 5
    1
      sonar-plugin-api/src/main/java/org/sonar/api/server/authentication/BaseIdentityProvider.java
  17. 2
    1
      sonar-plugin-api/src/main/java/org/sonar/api/server/authentication/OAuth2IdentityProvider.java

+ 6
- 1
server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationError.java View File

@@ -25,13 +25,14 @@ import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;

import static java.lang.String.format;
import static org.sonar.server.authentication.EmailAlreadyExistsException.EMAIL_ALREADY_EXISTS_PATH;
import static org.sonar.server.authentication.NotAllowUserToSignUpException.NOT_ALLOWED_TO_SIGHNUP_PATH;

public class AuthenticationError {

private static final Logger LOGGER = Loggers.get(AuthenticationError.class);

private static final String UNAUTHORIZED_PATH = "/sessions/unauthorized";
private static final String NOT_ALLOWED_TO_SIGHNUP_PATH = "/sessions/not_allowed_to_sign_up?providerName=%s";

private AuthenticationError() {
// Utility class
@@ -51,6 +52,10 @@ public class AuthenticationError {
redirectTo(response, format(NOT_ALLOWED_TO_SIGHNUP_PATH, e.getProvider().getName()));
}

public static void handleEmailAlreadyExistsError(EmailAlreadyExistsException e, HttpServletResponse response) {
redirectTo(response, format(EMAIL_ALREADY_EXISTS_PATH, e.getEmail()));
}

private static void redirectToUnauthorized(HttpServletResponse response) {
redirectTo(response, UNAUTHORIZED_PATH);
}

+ 35
- 0
server/sonar-server/src/main/java/org/sonar/server/authentication/EmailAlreadyExistsException.java View File

@@ -0,0 +1,35 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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;

public class EmailAlreadyExistsException extends RuntimeException {

public static final String EMAIL_ALREADY_EXISTS_PATH = "/sessions/email_already_exists?email=%s";

private final String email;

public EmailAlreadyExistsException(String email) {
this.email = email;
}

public String getEmail() {
return email;
}
}

+ 3
- 0
server/sonar-server/src/main/java/org/sonar/server/authentication/InitFilter.java View File

@@ -34,6 +34,7 @@ import org.sonar.api.web.ServletFilter;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static org.sonar.server.authentication.AuthenticationError.handleEmailAlreadyExistsError;
import static org.sonar.server.authentication.AuthenticationError.handleError;
import static org.sonar.server.authentication.AuthenticationError.handleNotAllowedToSignUpError;

@@ -78,6 +79,8 @@ public class InitFilter extends ServletFilter {
}
} catch (NotAllowUserToSignUpException e) {
handleNotAllowedToSignUpError(e, (HttpServletResponse) response);
} catch (EmailAlreadyExistsException e) {
handleEmailAlreadyExistsError(e, (HttpServletResponse) response);
} catch (Exception e) {
handleError(e, (HttpServletResponse) response, String.format("Fail to initialize authentication with provider '%s'", keyProvider));
}

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/authentication/NotAllowUserToSignUpException.java View File

@@ -23,6 +23,8 @@ import org.sonar.api.server.authentication.IdentityProvider;

public class NotAllowUserToSignUpException extends RuntimeException {

public static final String NOT_ALLOWED_TO_SIGHNUP_PATH = "/sessions/not_allowed_to_sign_up?providerName=%s";

private final IdentityProvider provider;

public NotAllowUserToSignUpException(IdentityProvider provider) {

+ 3
- 0
server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackFilter.java View File

@@ -31,6 +31,7 @@ import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import org.sonar.api.web.ServletFilter;

import static org.sonar.server.authentication.AuthenticationError.handleEmailAlreadyExistsError;
import static org.sonar.server.authentication.AuthenticationError.handleError;
import static org.sonar.server.authentication.AuthenticationError.handleNotAllowedToSignUpError;

@@ -67,6 +68,8 @@ public class OAuth2CallbackFilter extends ServletFilter {
}
} catch (NotAllowUserToSignUpException e) {
handleNotAllowedToSignUpError(e, (HttpServletResponse) response);
} catch (EmailAlreadyExistsException e) {
handleEmailAlreadyExistsError(e, (HttpServletResponse) response);
} catch (Exception e) {
handleError(e, (HttpServletResponse) response, String.format("Fail to callback authentication with %s", keyProvider));
}

+ 6
- 0
server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticator.java View File

@@ -66,6 +66,12 @@ public class UserIdentityAuthenticator {
if (!provider.allowsUsersToSignUp()) {
throw new NotAllowUserToSignUpException(provider);
}

String email = user.getEmail();
if (email != null && dbClient.userDao().doesEmailExist(dbSession, email)) {
throw new EmailAlreadyExistsException(email);
}

userUpdater.create(dbSession, NewUser.create()
.setLogin(uuidFactory.create())
.setEmail(user.getEmail())

+ 51
- 0
server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java View File

@@ -142,6 +142,28 @@ public class InitFilterTest {
assertError("Fail to initialize authentication with provider 'unsupported'");
}

@Test
public void redirect_when_failing_because_of_NotAllowUserToSignUpException() throws Exception {
IdentityProvider identityProvider = new FailWithNotAllowUserToSignUpIdProvider("failing");
when(request.getRequestURI()).thenReturn("/sessions/init/" + identityProvider.getKey());
identityProviderRepository.addIdentityProvider(identityProvider);

underTest.doFilter(request, response, chain);

verify(response).sendRedirect("/sessions/not_allowed_to_sign_up?providerName=Failing provider");
}

@Test
public void redirect_when_failing_because_of_EmailAlreadyExistsException() throws Exception {
IdentityProvider identityProvider = new FailWithEmailAlreadyExistsExceptionIdProvider("failing");
when(request.getRequestURI()).thenReturn("/sessions/init/" + identityProvider.getKey());
identityProviderRepository.addIdentityProvider(identityProvider);

underTest.doFilter(request, response, chain);

verify(response).sendRedirect("/sessions/email_already_exists?email=john@email.com");
}

private void assertOAuth2InitCalled(){
assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
assertThat(oAuth2IdentityProvider.isInitCalled()).isTrue();
@@ -157,4 +179,33 @@ public class InitFilterTest {
verify(response).sendRedirect("/sessions/unauthorized");
assertThat(oAuth2IdentityProvider.isInitCalled()).isFalse();
}

private static class FailWithNotAllowUserToSignUpIdProvider extends FakeBasicIdentityProvider {

public FailWithNotAllowUserToSignUpIdProvider(String key) {
super(key, true);
}

@Override
public String getName() {
return "Failing provider";
}

@Override
public void init(Context context) {
throw new NotAllowUserToSignUpException(this);
}
}

private static class FailWithEmailAlreadyExistsExceptionIdProvider extends FakeBasicIdentityProvider {

public FailWithEmailAlreadyExistsExceptionIdProvider(String key) {
super(key, true);
}

@Override
public void init(Context context) {
throw new EmailAlreadyExistsException("john@email.com");
}
}
}

+ 10
- 0
server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java View File

@@ -146,4 +146,14 @@ public class UserIdentityAuthenticatorTest {
thrown.expect(NotAllowUserToSignUpException.class);
underTest.authenticate(USER_IDENTITY, identityProvider, httpSession);
}

@Test
public void fail_to_authenticate_new_user_when_email_already_exists() throws Exception {
when(userDao.selectByExternalIdentity(dbSession, USER_IDENTITY.getId(), IDENTITY_PROVIDER.getKey())).thenReturn(Optional.<UserDto>absent());
when(userDao.selectOrFailByExternalIdentity(dbSession, USER_IDENTITY.getId(), IDENTITY_PROVIDER.getKey())).thenReturn(ACTIVE_USER);
when(userDao.doesEmailExist(dbSession, USER_IDENTITY.getEmail())).thenReturn(true);

thrown.expect(EmailAlreadyExistsException.class);
underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, httpSession);
}
}

+ 10
- 0
server/sonar-web/src/main/webapp/WEB-INF/app/views/sessions/email_already_exists.erb View File

@@ -0,0 +1,10 @@
<table class="spaced">
<tr>
<td align="center">

<div id="login_form">
<p id="unauthorized">Email <%= h params[:email] %> is already used by an existing user</p>
</div>
</td>
</tr>
</table>

+ 1
- 1
server/sonar-web/src/main/webapp/WEB-INF/app/views/sessions/not_allowed_to_sign_up.html.erb View File

@@ -3,7 +3,7 @@
<td align="center">

<div id="login_form">
<p id="unauthorized"><%= params[:providerName] %> users are not allowed to signup</p>
<p id="unauthorized"><%= h params[:providerName] %> users are not allowed to signup</p>
</div>
</td>
</tr>

+ 7
- 0
sonar-db/src/main/java/org/sonar/db/user/UserDao.java View File

@@ -187,6 +187,13 @@ public class UserDao implements Dao {
throw new RowNotFoundException(String.format("User with identity provider '%s' and id '%s' has not been found", extIdentityProvider, extIdentity));
}

/**
* Please note that email is case insensitive, result for searching 'mail@email.com' or 'Mail@Email.com' will be the same
*/
public boolean doesEmailExist(DbSession dbSession, String email){
return mapper(dbSession).countByEmail(email.toLowerCase()) > 0;
}

protected UserMapper mapper(DbSession session) {
return session.getMapper(UserMapper.class);
}

+ 2
- 0
sonar-db/src/main/java/org/sonar/db/user/UserMapper.java View File

@@ -57,6 +57,8 @@ public interface UserMapper {
@CheckForNull
GroupDto selectGroupByName(String name);

long countByEmail(String email);

void insert(UserDto userDto);

void update(UserDto userDto);

+ 6
- 0
sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml View File

@@ -98,6 +98,12 @@
</where>
</select>

<select id="countByEmail" parameterType="String" resultType="long">
SELECT count(u.id)
FROM users u
where lower(u.email)=#{email}
</select>

<select id="selectGroupByName" parameterType="string" resultType="Group">
SELECT id, name, description, created_at AS "createdAt", updated_at AS "updatedAt"
FROM groups WHERE name=#{id}

+ 9
- 0
sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java View File

@@ -342,4 +342,13 @@ public class UserDaoTest {
thrown.expectMessage("User with identity provider 'unknown' and id 'unknown' has not been found");
underTest.selectOrFailByExternalIdentity(session, "unknown", "unknown");
}

@Test
public void exists_by_email() throws Exception {
db.prepareDbUnit(getClass(), "exists_by_email.xml");

assertThat(underTest.doesEmailExist(session, "marius@lesbronzes.fr")).isTrue();
assertThat(underTest.doesEmailExist(session, "Marius@LesBronzes.fr")).isTrue();
assertThat(underTest.doesEmailExist(session, "unknown")).isFalse();
}
}

+ 8
- 0
sonar-db/src/test/resources/org/sonar/db/user/UserDaoTest/exists_by_email.xml View File

@@ -0,0 +1,8 @@
<dataset>

<users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" active="[true]" scm_accounts="&#10;ma&#10;marius33&#10;" created_at="1418215735482" updated_at="1418215735485"
salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"/>
<users id="102" login="sbrandhof" name="Simon Brandhof" email="marius@lesbronzes.fr" active="[true]" scm_accounts="[null]" created_at="1418215735482" updated_at="1418215735485"
salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8366" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fh"/>

</dataset>

+ 5
- 1
sonar-plugin-api/src/main/java/org/sonar/api/server/authentication/BaseIdentityProvider.java View File

@@ -59,7 +59,11 @@ public interface BaseIdentityProvider extends IdentityProvider {
* The first time a user is authenticated (and if {@link #allowsUsersToSignUp()} is true), a new user will be registered.
* Then, only user's name and email are updated.
*
* @throws NotAllowUserToSignUpException when {@link #allowsUsersToSignUp()} is false and a new user try to authenticate
* If @link #allowsUsersToSignUp()} is set to false and a new user try to authenticate,
* then the user is not authenticated and he's redirected to a dedicated page.
*
* If the email of the user is already used by an existing user of the platform,
* then the user is not authenticated and he's redirected to a dedicated page.
*/
void authenticate(UserIdentity userIdentity);


+ 2
- 1
sonar-plugin-api/src/main/java/org/sonar/api/server/authentication/OAuth2IdentityProvider.java View File

@@ -87,7 +87,8 @@ public interface OAuth2IdentityProvider extends IdentityProvider {
void redirectToRequestedPage();

/**
* Authenticate and register the user into the platform
* Authenticate and register the user into the platform.
* @see org.sonar.api.server.authentication.BaseIdentityProvider.Context#authenticate(UserIdentity)
*/
void authenticate(UserIdentity userIdentity);
}

Loading…
Cancel
Save