diff options
author | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2019-08-12 15:24:22 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-08-14 20:21:14 +0200 |
commit | 2528f0d148c0f0c0f6e4022c423ac5d67dcb650a (patch) | |
tree | cfdd97e8868cffa3beb0d74011e3eec9dd7ab973 /server/sonar-server | |
parent | e9ae396829a95e449d2e02934fc0567e7f09f833 (diff) | |
download | sonarqube-2528f0d148c0f0c0f6e4022c423ac5d67dcb650a.tar.gz sonarqube-2528f0d148c0f0c0f6e4022c423ac5d67dcb650a.zip |
create webserver-auth and webserver-common from sonar-server
actual module names are sonar-webserver-auth and sonar-webserver-common
Diffstat (limited to 'server/sonar-server')
203 files changed, 12 insertions, 26914 deletions
diff --git a/server/sonar-server/build.gradle b/server/sonar-server/build.gradle index d15997a4a14..f225dbac9ca 100644 --- a/server/sonar-server/build.gradle +++ b/server/sonar-server/build.gradle @@ -50,6 +50,8 @@ dependencies { compile project(':server:sonar-db-migration') compile project(':server:sonar-process') compile project(':server:sonar-server-common') + compile project(':server:sonar-webserver-auth') + compile project(':server:sonar-webserver-common') compile project(':server:sonar-webserver-ws') compile project(':sonar-core') compile project(':sonar-duplications') @@ -80,8 +82,9 @@ dependencies { testCompile 'org.subethamail:subethasmtp' testCompile project(':server:sonar-db-testing') testCompile project(path: ":server:sonar-server-common", configuration: "tests") - testCompile project(':sonar-testing-harness') + testCompile project(path: ":server:sonar-webserver-auth", configuration: "tests") testCompile project(path: ":server:sonar-webserver-ws", configuration: "tests") + testCompile project(':sonar-testing-harness') runtime 'io.jsonwebtoken:jjwt-jackson' } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationError.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationError.java deleted file mode 100644 index b0a79c33687..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationError.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.server.authentication.event.AuthenticationException; - -import static org.sonar.server.authentication.AuthenticationRedirection.encodeMessage; -import static org.sonar.server.authentication.AuthenticationRedirection.redirectTo; -import static org.sonar.server.authentication.Cookies.newCookieBuilder; - -public final class AuthenticationError { - - private static final String UNAUTHORIZED_PATH = "/sessions/unauthorized"; - - private static final Logger LOGGER = Loggers.get(AuthenticationError.class); - private static final String AUTHENTICATION_ERROR_COOKIE = "AUTHENTICATION-ERROR"; - private static final int FIVE_MINUTES_IN_SECONDS = 5 * 60; - - private AuthenticationError() { - // Utility class - } - - static void handleError(Exception e, HttpServletRequest request, HttpServletResponse response, String message) { - LOGGER.warn(message, e); - redirectToUnauthorized(request, response); - } - - static void handleError(HttpServletRequest request, HttpServletResponse response, String message) { - LOGGER.warn(message); - redirectToUnauthorized(request, response); - } - - static void handleAuthenticationError(AuthenticationException e, HttpServletRequest request, HttpServletResponse response) { - String publicMessage = e.getPublicMessage(); - if (publicMessage != null && !publicMessage.isEmpty()) { - addErrorCookie(request, response, publicMessage); - } - redirectToUnauthorized(request, response); - } - - public static void addErrorCookie(HttpServletRequest request, HttpServletResponse response, String value) { - response.addCookie(newCookieBuilder(request) - .setName(AUTHENTICATION_ERROR_COOKIE) - .setValue(encodeMessage(value)) - .setHttpOnly(false) - .setExpiry(FIVE_MINUTES_IN_SECONDS) - .build()); - } - - private static void redirectToUnauthorized(HttpServletRequest request, HttpServletResponse response) { - redirectTo(response, request.getContextPath() + UNAUTHORIZED_PATH); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationFilter.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationFilter.java deleted file mode 100644 index 244ccfbc2a8..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationFilter.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.annotation.CheckForNull; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.server.authentication.IdentityProvider; -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.handleError; - -public abstract class AuthenticationFilter extends ServletFilter { - - static final String CALLBACK_PATH = "/oauth2/callback/"; - private final IdentityProviderRepository identityProviderRepository; - - public AuthenticationFilter(IdentityProviderRepository identityProviderRepository) { - this.identityProviderRepository = identityProviderRepository; - } - - /** - * @return the {@link IdentityProvider} for the key extracted in the request if is exists, or {@code null}, in which - * case the request is fully handled and caller should not handle it - */ - @CheckForNull - IdentityProvider resolveProviderOrHandleResponse(HttpServletRequest request, HttpServletResponse response, String path) { - String requestUri = request.getRequestURI(); - String providerKey = extractKeyProvider(requestUri, request.getContextPath() + path); - if (providerKey == null) { - handleError(request, response, "No provider key found in URI"); - return null; - } - try { - return identityProviderRepository.getEnabledByKey(providerKey); - } catch (Exception e) { - handleError(e, request, response, format("Failed to retrieve IdentityProvider for key '%s'", providerKey)); - return null; - } - } - - @CheckForNull - private static String extractKeyProvider(String requestUri, String context) { - if (requestUri.contains(context)) { - String key = requestUri.replace(context, ""); - if (!isNullOrEmpty(key)) { - return key; - } - } - return null; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java deleted file mode 100644 index 8309447b400..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationModule.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.sonar.core.platform.Module; -import org.sonar.server.authentication.event.AuthenticationEventImpl; - -public class AuthenticationModule extends Module { - @Override - protected void configureModule() { - add( - AuthenticationEventImpl.class, - InitFilter.class, - OAuth2CallbackFilter.class, - IdentityProviderRepository.class, - BaseContextFactory.class, - OAuth2ContextFactory.class, - UserRegistrarImpl.class, - OAuthCsrfVerifier.class, - UserSessionInitializer.class, - JwtSerializer.class, - JwtHttpHandler.class, - JwtCsrfVerifier.class, - OAuth2AuthenticationParametersImpl.class, - CredentialsAuthentication.class, - CredentialsLocalAuthentication.class, - CredentialsExternalAuthentication.class, - BasicAuthentication.class, - HttpHeadersAuthentication.class, - RequestAuthenticatorImpl.class, - UserLastConnectionDatesUpdaterImpl.class); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationRedirection.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationRedirection.java deleted file mode 100644 index b3e91772dd1..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/AuthenticationRedirection.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import javax.servlet.http.HttpServletResponse; - -import static java.lang.String.format; -import static java.net.URLEncoder.encode; -import static java.nio.charset.StandardCharsets.UTF_8; - -public class AuthenticationRedirection { - - private AuthenticationRedirection() { - // Only static methods - } - - public static String encodeMessage(String message) { - try { - return encode(message, UTF_8.name()) - // In order for Javascript to be able to decode this message, + must be replaced by %20 - .replace("+", "%20"); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(format("Fail to encode %s", message), e); - } - } - - public static void redirectTo(HttpServletResponse response, String url) { - try { - response.sendRedirect(url); - } catch (IOException e) { - throw new IllegalStateException(format("Fail to redirect to %s", url), e); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/BaseContextFactory.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/BaseContextFactory.java deleted file mode 100644 index 0c9385b2e4e..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/BaseContextFactory.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.platform.Server; -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; - -public class BaseContextFactory { - - private final ThreadLocalUserSession threadLocalUserSession; - private final UserRegistrar userRegistrar; - private final Server server; - private final JwtHttpHandler jwtHttpHandler; - private final UserSessionFactory userSessionFactory; - - public BaseContextFactory(UserRegistrar userRegistrar, Server server, JwtHttpHandler jwtHttpHandler, - ThreadLocalUserSession threadLocalUserSession, UserSessionFactory userSessionFactory) { - this.userSessionFactory = userSessionFactory; - this.userRegistrar = userRegistrar; - this.server = server; - this.jwtHttpHandler = jwtHttpHandler; - this.threadLocalUserSession = threadLocalUserSession; - } - - public BaseIdentityProvider.Context newContext(HttpServletRequest request, HttpServletResponse response, BaseIdentityProvider identityProvider) { - return new ContextImpl(request, response, identityProvider); - } - - private class ContextImpl implements BaseIdentityProvider.Context { - private final HttpServletRequest request; - private final HttpServletResponse response; - private final BaseIdentityProvider identityProvider; - - public ContextImpl(HttpServletRequest request, HttpServletResponse response, BaseIdentityProvider identityProvider) { - this.request = request; - this.response = response; - this.identityProvider = identityProvider; - } - - @Override - public HttpServletRequest getRequest() { - return request; - } - - @Override - public HttpServletResponse getResponse() { - return response; - } - - @Override - public String getServerBaseURL() { - return server.getPublicRootUrl(); - } - - @Override - public void authenticate(UserIdentity userIdentity) { - UserDto userDto = userRegistrar.register( - UserRegistration.builder() - .setUserIdentity(userIdentity) - .setProvider(identityProvider) - .setSource(Source.external(identityProvider)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - jwtHttpHandler.generateToken(userDto, request, response); - threadLocalUserSession.set(userSessionFactory.create(userDto)); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthentication.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthentication.java deleted file mode 100644 index 71dbd0428fa..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/BasicAuthentication.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Base64; -import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.event.AuthenticationEvent; -import org.sonar.server.authentication.event.AuthenticationException; -import org.sonar.server.usertoken.UserTokenAuthentication; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.apache.commons.lang.StringUtils.startsWithIgnoreCase; -import static org.sonar.server.authentication.event.AuthenticationEvent.Method; -import static org.sonar.server.authentication.event.AuthenticationEvent.Source; - -/** - * HTTP BASIC authentication relying on tuple {login, password}. - * Login can represent a user access token. - * - * @see CredentialsAuthentication for standard login/password authentication - * @see UserTokenAuthentication for user access token - */ -public class BasicAuthentication { - - private final DbClient dbClient; - private final CredentialsAuthentication credentialsAuthentication; - private final UserTokenAuthentication userTokenAuthentication; - private final AuthenticationEvent authenticationEvent; - - public BasicAuthentication(DbClient dbClient, CredentialsAuthentication credentialsAuthentication, - UserTokenAuthentication userTokenAuthentication, AuthenticationEvent authenticationEvent) { - this.dbClient = dbClient; - this.credentialsAuthentication = credentialsAuthentication; - this.userTokenAuthentication = userTokenAuthentication; - this.authenticationEvent = authenticationEvent; - } - - public Optional<UserDto> authenticate(HttpServletRequest request) { - return extractCredentialsFromHeader(request) - .flatMap(credentials -> Optional.of(authenticate(credentials, request))); - } - - public static Optional<Credentials> extractCredentialsFromHeader(HttpServletRequest request) { - String authorizationHeader = request.getHeader("Authorization"); - if (authorizationHeader == null || !startsWithIgnoreCase(authorizationHeader, "BASIC")) { - return Optional.empty(); - } - - String basicAuthEncoded = authorizationHeader.substring(6); - String basicAuthDecoded = getDecodedBasicAuth(basicAuthEncoded); - - int semiColonPos = basicAuthDecoded.indexOf(':'); - if (semiColonPos <= 0) { - throw AuthenticationException.newBuilder() - .setSource(Source.local(Method.BASIC)) - .setMessage("Decoded basic auth does not contain ':'") - .build(); - } - String login = basicAuthDecoded.substring(0, semiColonPos); - String password = basicAuthDecoded.substring(semiColonPos + 1); - return Optional.of(new Credentials(login, password)); - } - - private static String getDecodedBasicAuth(String basicAuthEncoded) { - try { - return new String(Base64.getDecoder().decode(basicAuthEncoded.getBytes(UTF_8)), UTF_8); - } catch (Exception e) { - throw AuthenticationException.newBuilder() - .setSource(Source.local(Method.BASIC)) - .setMessage("Invalid basic header") - .build(); - } - } - - private UserDto authenticate(Credentials credentials, HttpServletRequest request) { - if (!credentials.getPassword().isPresent()) { - UserDto userDto = authenticateFromUserToken(credentials.getLogin()); - authenticationEvent.loginSuccess(request, userDto.getLogin(), Source.local(Method.BASIC_TOKEN)); - return userDto; - } - return credentialsAuthentication.authenticate(credentials, request, Method.BASIC); - } - - private UserDto authenticateFromUserToken(String token) { - Optional<String> authenticatedUserUuid = userTokenAuthentication.authenticate(token); - if (!authenticatedUserUuid.isPresent()) { - throw AuthenticationException.newBuilder() - .setSource(Source.local(Method.BASIC_TOKEN)) - .setMessage("Token doesn't exist") - .build(); - } - try (DbSession dbSession = dbClient.openSession(false)) { - UserDto userDto = dbClient.userDao().selectByUuid(dbSession, authenticatedUserUuid.get()); - if (userDto == null || !userDto.isActive()) { - throw AuthenticationException.newBuilder() - .setSource(Source.local(Method.BASIC_TOKEN)) - .setMessage("User doesn't exist") - .build(); - } - return userDto; - } - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/Cookies.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/Cookies.java deleted file mode 100644 index 1af5276cbcd..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/Cookies.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Arrays; -import java.util.Optional; -import javax.annotation.Nullable; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; - -import static com.google.common.base.Strings.isNullOrEmpty; -import static java.util.Objects.requireNonNull; - -/** - * Helper class to create a {@link javax.servlet.http.Cookie}. - * - * The {@link javax.servlet.http.Cookie#setSecure(boolean)} will automatically be set to true. - */ -public class Cookies { - - private static final String HTTPS_HEADER = "X-Forwarded-Proto"; - private static final String HTTPS_VALUE = "https"; - - private Cookies() { - // Only static methods - } - - public static Optional<Cookie> findCookie(String cookieName, HttpServletRequest request) { - Cookie[] cookies = request.getCookies(); - if (cookies == null) { - return Optional.empty(); - } - return Arrays.stream(cookies) - .filter(cookie -> cookieName.equals(cookie.getName())) - .findFirst(); - } - - public static CookieBuilder newCookieBuilder(HttpServletRequest request) { - return new CookieBuilder(request); - } - - public static class CookieBuilder { - - private final HttpServletRequest request; - - private String name; - private String value; - private boolean httpOnly; - private int expiry; - - CookieBuilder(HttpServletRequest request) { - this.request = request; - } - - /** - * Name of the cookie - */ - public CookieBuilder setName(String name) { - this.name = requireNonNull(name); - return this; - } - - /** - * Name of the cookie - */ - public CookieBuilder setValue(@Nullable String value) { - this.value = value; - return this; - } - - /** - * Sets the flag that controls if this cookie will be hidden from scripts on the client side. - */ - public CookieBuilder setHttpOnly(boolean httpOnly) { - this.httpOnly = httpOnly; - return this; - } - - /** - * Sets the maximum age of the cookie in seconds. - */ - public CookieBuilder setExpiry(int expiry) { - this.expiry = expiry; - return this; - } - - public Cookie build() { - Cookie cookie = new Cookie(requireNonNull(name), value); - cookie.setPath(getContextPath(request)); - cookie.setSecure(isHttps(request)); - cookie.setHttpOnly(httpOnly); - cookie.setMaxAge(expiry); - return cookie; - } - - private static boolean isHttps(HttpServletRequest request) { - return HTTPS_VALUE.equalsIgnoreCase(request.getHeader(HTTPS_HEADER)); - } - - private static String getContextPath(HttpServletRequest request) { - String path = request.getContextPath(); - return isNullOrEmpty(path) ? "/" : path; - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/Credentials.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/Credentials.java deleted file mode 100644 index fc92b9580c0..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/Credentials.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Objects; -import java.util.Optional; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -import static com.google.common.base.Preconditions.checkArgument; -import static org.apache.commons.lang.StringUtils.defaultIfEmpty; - -@Immutable -public class Credentials { - - private final String login; - private final String password; - - public Credentials(String login, @Nullable String password) { - checkArgument(login != null && !login.isEmpty(), "login must not be null nor empty"); - this.login = login; - this.password = defaultIfEmpty(password, null); - } - - /** - * Non-empty login - */ - public String getLogin() { - return login; - } - - /** - * Non-empty password. {@code Optional.empty()} is returned if the password is not set - * or initially empty. {@code Optional.of("")} is never returned. - */ - public Optional<String> getPassword() { - return Optional.ofNullable(password); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Credentials that = (Credentials) o; - return login.equals(that.login) && password.equals(that.password); - } - - @Override - public int hashCode() { - return Objects.hash(login, password); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthentication.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthentication.java deleted file mode 100644 index 52083037cab..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsAuthentication.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.event.AuthenticationEvent; -import org.sonar.server.authentication.event.AuthenticationException; - -import static org.sonar.server.authentication.event.AuthenticationEvent.Method; -import static org.sonar.server.authentication.event.AuthenticationEvent.Source; - -/** - * Authentication based on the tuple {login, password}. Validation can be - * delegated to an external system, e.g. LDAP. - */ -public class CredentialsAuthentication { - - private final DbClient dbClient; - private final AuthenticationEvent authenticationEvent; - private final CredentialsExternalAuthentication externalAuthentication; - private final CredentialsLocalAuthentication localAuthentication; - - public CredentialsAuthentication(DbClient dbClient, AuthenticationEvent authenticationEvent, - CredentialsExternalAuthentication externalAuthentication, CredentialsLocalAuthentication localAuthentication) { - this.dbClient = dbClient; - this.authenticationEvent = authenticationEvent; - this.externalAuthentication = externalAuthentication; - this.localAuthentication = localAuthentication; - } - - public UserDto authenticate(Credentials credentials, HttpServletRequest request, Method method) { - try (DbSession dbSession = dbClient.openSession(false)) { - return authenticate(dbSession, credentials, request, method); - } - } - - private UserDto authenticate(DbSession dbSession, Credentials credentials, HttpServletRequest request, Method method) { - UserDto localUser = dbClient.userDao().selectActiveUserByLogin(dbSession, credentials.getLogin()); - if (localUser != null && localUser.isLocal()) { - localAuthentication.authenticate(dbSession, localUser, credentials.getPassword().orElse(null), method); - dbSession.commit(); - authenticationEvent.loginSuccess(request, localUser.getLogin(), Source.local(method)); - return localUser; - } - Optional<UserDto> externalUser = externalAuthentication.authenticate(credentials, request, method); - if (externalUser.isPresent()) { - return externalUser.get(); - } - throw AuthenticationException.newBuilder() - .setSource(Source.local(method)) - .setLogin(credentials.getLogin()) - .setMessage(localUser != null && !localUser.isLocal() ? "User is not local" : "No active user for login") - .build(); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsExternalAuthentication.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsExternalAuthentication.java deleted file mode 100644 index 6a0187e35c9..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsExternalAuthentication.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Locale; -import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import org.sonar.api.Startable; -import org.sonar.api.config.Configuration; -import org.sonar.api.security.Authenticator; -import org.sonar.api.security.ExternalGroupsProvider; -import org.sonar.api.security.ExternalUsersProvider; -import org.sonar.api.security.SecurityRealm; -import org.sonar.api.security.UserDetails; -import org.sonar.api.server.authentication.Display; -import org.sonar.api.server.authentication.IdentityProvider; -import org.sonar.api.server.authentication.UserIdentity; -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; -import org.sonar.server.user.SecurityRealmFactory; - -import static java.util.Objects.requireNonNull; -import static org.apache.commons.lang.StringUtils.isEmpty; -import static org.apache.commons.lang.StringUtils.trimToNull; -import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY; - -/** - * Delegates the validation of credentials to an external system, e.g. LDAP. - */ -public class CredentialsExternalAuthentication implements Startable { - - private static final Logger LOG = Loggers.get(CredentialsExternalAuthentication.class); - - private final Configuration config; - private final SecurityRealmFactory securityRealmFactory; - private final UserRegistrar userRegistrar; - private final AuthenticationEvent authenticationEvent; - - private SecurityRealm realm; - private Authenticator authenticator; - private ExternalUsersProvider externalUsersProvider; - private ExternalGroupsProvider externalGroupsProvider; - - public CredentialsExternalAuthentication(Configuration config, SecurityRealmFactory securityRealmFactory, - UserRegistrar userRegistrar, AuthenticationEvent authenticationEvent) { - this.config = config; - this.securityRealmFactory = securityRealmFactory; - this.userRegistrar = userRegistrar; - this.authenticationEvent = authenticationEvent; - } - - @Override - public void start() { - realm = securityRealmFactory.getRealm(); - if (realm != null) { - authenticator = requireNonNull(realm.doGetAuthenticator(), "No authenticator available"); - externalUsersProvider = requireNonNull(realm.getUsersProvider(), "No users provider available"); - externalGroupsProvider = realm.getGroupsProvider(); - } - } - - public Optional<UserDto> authenticate(Credentials credentials, HttpServletRequest request, AuthenticationEvent.Method method) { - if (realm == null) { - return Optional.empty(); - } - return Optional.of(doAuthenticate(fixCase(credentials), request, method)); - } - - private UserDto doAuthenticate(Credentials credentials, HttpServletRequest request, AuthenticationEvent.Method method) { - try { - ExternalUsersProvider.Context externalUsersProviderContext = new ExternalUsersProvider.Context(credentials.getLogin(), request); - UserDetails details = externalUsersProvider.doGetUserDetails(externalUsersProviderContext); - if (details == null) { - throw AuthenticationException.newBuilder() - .setSource(realmEventSource(method)) - .setLogin(credentials.getLogin()) - .setMessage("No user details") - .build(); - } - Authenticator.Context authenticatorContext = new Authenticator.Context(credentials.getLogin(), credentials.getPassword().orElse(null), request); - boolean status = authenticator.doAuthenticate(authenticatorContext); - if (!status) { - throw AuthenticationException.newBuilder() - .setSource(realmEventSource(method)) - .setLogin(credentials.getLogin()) - .setMessage("Realm returned authenticate=false") - .build(); - } - UserDto userDto = synchronize(credentials.getLogin(), details, request, method); - authenticationEvent.loginSuccess(request, credentials.getLogin(), realmEventSource(method)); - return userDto; - } catch (AuthenticationException e) { - throw e; - } catch (Exception e) { - // It seems that with Realm API it's expected to log the error and to not authenticate the user - LOG.error("Error during authentication", e); - throw AuthenticationException.newBuilder() - .setSource(realmEventSource(method)) - .setLogin(credentials.getLogin()) - .setMessage(e.getMessage()) - .build(); - } - } - - private Source realmEventSource(AuthenticationEvent.Method method) { - return Source.realm(method, realm.getName()); - } - - private UserDto synchronize(String userLogin, UserDetails details, HttpServletRequest request, AuthenticationEvent.Method method) { - String name = details.getName(); - UserIdentity.Builder userIdentityBuilder = UserIdentity.builder() - .setLogin(userLogin) - .setName(isEmpty(name) ? userLogin : name) - .setEmail(trimToNull(details.getEmail())) - .setProviderLogin(userLogin); - if (externalGroupsProvider != null) { - ExternalGroupsProvider.Context context = new ExternalGroupsProvider.Context(userLogin, request); - Collection<String> groups = externalGroupsProvider.doGetGroups(context); - userIdentityBuilder.setGroups(new HashSet<>(groups)); - } - return userRegistrar.register( - UserRegistration.builder() - .setUserIdentity(userIdentityBuilder.build()) - .setProvider(new ExternalIdentityProvider()) - .setSource(realmEventSource(method)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - } - - private Credentials fixCase(Credentials credentials) { - if (config.getBoolean("sonar.authenticator.downcase").orElse(false)) { - return new Credentials(credentials.getLogin().toLowerCase(Locale.ENGLISH), credentials.getPassword().orElse(null)); - } - return credentials; - } - - private static class ExternalIdentityProvider implements IdentityProvider { - @Override - public String getKey() { - return SQ_AUTHORITY; - } - - @Override - public String getName() { - return SQ_AUTHORITY; - } - - @Override - public Display getDisplay() { - return null; - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public boolean allowsUsersToSignUp() { - return true; - } - } - - @Override - public void stop() { - // Nothing to do - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsLocalAuthentication.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsLocalAuthentication.java deleted file mode 100644 index de76fc27447..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/CredentialsLocalAuthentication.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.security.SecureRandom; -import javax.annotation.Nullable; -import org.apache.commons.codec.digest.DigestUtils; -import org.mindrot.jbcrypt.BCrypt; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.event.AuthenticationEvent.Method; -import org.sonar.server.authentication.event.AuthenticationEvent.Source; -import org.sonar.server.authentication.event.AuthenticationException; - -import static com.google.common.base.Preconditions.checkArgument; -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; - -/** - * Validates the password of a "local" user (password is stored in - * database). - */ -public class CredentialsLocalAuthentication { - - private final DbClient dbClient; - private static final SecureRandom SECURE_RANDOM = new SecureRandom(); - // The default hash method that must be used is BCRYPT - private static final HashMethod DEFAULT = HashMethod.BCRYPT; - - public CredentialsLocalAuthentication(DbClient dbClient) { - this.dbClient = dbClient; - } - - /** - * This method authenticate a user with his password against the value stored in user. - * If authentication failed an AuthenticationException will be thrown containing the failure message. - * If the password must be updated because an old algorithm is used, the UserDto is updated but the session - * is not committed - */ - public void authenticate(DbSession session, UserDto user, @Nullable String password, Method method) { - if (user.getHashMethod() == null) { - throw AuthenticationException.newBuilder() - .setSource(Source.local(method)) - .setLogin(user.getLogin()) - .setMessage("null hash method") - .build(); - } - - HashMethod hashMethod; - try { - hashMethod = HashMethod.valueOf(user.getHashMethod()); - } catch (IllegalArgumentException ex) { - throw AuthenticationException.newBuilder() - .setSource(Source.local(method)) - .setLogin(user.getLogin()) - .setMessage(format("Unknown hash method [%s]", user.getHashMethod())) - .build(); - } - - AuthenticationResult result = hashMethod.checkCredentials(user, password); - if (!result.isSuccessful()) { - throw AuthenticationException.newBuilder() - .setSource(Source.local(method)) - .setLogin(user.getLogin()) - .setMessage(result.getFailureMessage()) - .build(); - } - - // Upgrade the password if it's an old hashMethod - if (hashMethod != DEFAULT) { - DEFAULT.storeHashPassword(user, password); - dbClient.userDao().update(session, user); - } - } - - /** - * Method used to store the password as a hash in database. - * The crypted_password, salt and hash_method are set - */ - public void storeHashPassword(UserDto user, String password) { - DEFAULT.storeHashPassword(user, password); - } - - public enum HashMethod implements HashFunction { - SHA1(new Sha1Function()), BCRYPT(new BcryptFunction()); - - private HashFunction hashFunction; - - HashMethod(HashFunction hashFunction) { - this.hashFunction = hashFunction; - } - - @Override - public AuthenticationResult checkCredentials(UserDto user, String password) { - return hashFunction.checkCredentials(user, password); - } - - @Override - public void storeHashPassword(UserDto user, String password) { - hashFunction.storeHashPassword(user, password); - } - } - - private static class AuthenticationResult { - private final boolean successful; - private final String failureMessage; - - private AuthenticationResult(boolean successful, String failureMessage) { - checkArgument((successful && failureMessage.isEmpty()) || (!successful && !failureMessage.isEmpty()), "Incorrect parameters"); - this.successful = successful; - this.failureMessage = failureMessage; - } - - public boolean isSuccessful() { - return successful; - } - - public String getFailureMessage() { - return failureMessage; - } - } - - public interface HashFunction { - AuthenticationResult checkCredentials(UserDto user, String password); - - void storeHashPassword(UserDto user, String password); - } - - /** - * Implementation of deprecated SHA1 hash function - */ - private static final class Sha1Function implements HashFunction { - @Override - public AuthenticationResult checkCredentials(UserDto user, String password) { - if (user.getCryptedPassword() == null) { - return new AuthenticationResult(false, "null password in DB"); - } - if (user.getSalt() == null) { - return new AuthenticationResult(false, "null salt"); - } - if (!user.getCryptedPassword().equals(hash(user.getSalt(), password))) { - return new AuthenticationResult(false, "wrong password"); - } - return new AuthenticationResult(true, ""); - } - - @Override - public void storeHashPassword(UserDto user, String password) { - requireNonNull(password, "Password cannot be null"); - byte[] saltRandom = new byte[20]; - SECURE_RANDOM.nextBytes(saltRandom); - String salt = DigestUtils.sha1Hex(saltRandom); - - user.setHashMethod(HashMethod.SHA1.name()) - .setCryptedPassword(hash(salt, password)) - .setSalt(salt); - } - - private static String hash(String salt, String password) { - return DigestUtils.sha1Hex("--" + salt + "--" + password + "--"); - } - } - - /** - * Implementation of bcrypt hash function - */ - private static final class BcryptFunction implements HashFunction { - @Override - public AuthenticationResult checkCredentials(UserDto user, String password) { - if (!BCrypt.checkpw(password, user.getCryptedPassword())) { - return new AuthenticationResult(false, "wrong password"); - } - return new AuthenticationResult(true, ""); - } - - @Override - public void storeHashPassword(UserDto user, String password) { - requireNonNull(password, "Password cannot be null"); - user.setHashMethod(HashMethod.BCRYPT.name()) - .setCryptedPassword(BCrypt.hashpw(password, BCrypt.gensalt(12))) - .setSalt(null); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/CustomAuthentication.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/CustomAuthentication.java deleted file mode 100644 index ee80b12c135..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/CustomAuthentication.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.server.ServerSide; -import org.sonar.server.user.UserSession; - -/** - * Authentication that can create {@link org.sonar.server.user.UserSession} - * that are not associated to a user. - * That is convenient for authenticating bots that need special permissions. - * - * This is not an extension point, plugins can not provide their own - * implementations. - */ -@ServerSide -public interface CustomAuthentication { - - Optional<UserSession> authenticate(HttpServletRequest request, HttpServletResponse response); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java deleted file mode 100644 index 29ed2e65195..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableMap; -import java.util.Collections; -import java.util.Date; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import javax.annotation.CheckForNull; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.Startable; -import org.sonar.api.config.Configuration; -import org.sonar.api.server.authentication.Display; -import org.sonar.api.server.authentication.IdentityProvider; -import org.sonar.api.server.authentication.UserIdentity; -import org.sonar.api.utils.System2; -import org.sonar.api.utils.log.Logger; -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; -import org.sonar.server.exceptions.BadRequestException; - -import static org.apache.commons.lang.time.DateUtils.addMinutes; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_EMAIL_HEADER; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_ENABLE; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_GROUPS_HEADER; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_LOGIN_HEADER; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_NAME_HEADER; -import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES; -import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY; - -/** - * Authentication based on the HTTP request headers. The front proxy - * is responsible for validating user identity. - */ -public class HttpHeadersAuthentication implements Startable { - - private static final Logger LOG = Loggers.get(HttpHeadersAuthentication.class); - - private static final Splitter COMA_SPLITTER = Splitter.on(",").trimResults().omitEmptyStrings(); - - private static final String LAST_REFRESH_TIME_TOKEN_PARAM = "ssoLastRefreshTime"; - - private static final EnumSet<ProcessProperties.Property> PROPERTIES = EnumSet.of( - SONAR_WEB_SSO_LOGIN_HEADER, - SONAR_WEB_SSO_NAME_HEADER, - SONAR_WEB_SSO_EMAIL_HEADER, - SONAR_WEB_SSO_GROUPS_HEADER, - SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES); - - private final System2 system2; - private final Configuration config; - private final UserRegistrar userRegistrar; - private final JwtHttpHandler jwtHttpHandler; - private final AuthenticationEvent authenticationEvent; - private final Map<String, String> settingsByKey = new HashMap<>(); - - private boolean enabled = false; - - public HttpHeadersAuthentication(System2 system2, Configuration config, UserRegistrar userRegistrar, - JwtHttpHandler jwtHttpHandler, AuthenticationEvent authenticationEvent) { - this.system2 = system2; - this.config = config; - this.userRegistrar = userRegistrar; - this.jwtHttpHandler = jwtHttpHandler; - this.authenticationEvent = authenticationEvent; - } - - @Override - public void start() { - if (config.getBoolean(SONAR_WEB_SSO_ENABLE.getKey()).orElse(false)) { - LOG.info("HTTP headers authentication enabled"); - enabled = true; - PROPERTIES.forEach(entry -> settingsByKey.put(entry.getKey(), config.get(entry.getKey()).orElse(entry.getDefaultValue()))); - } - } - - @Override - public void stop() { - // Nothing to do - } - - public Optional<UserDto> authenticate(HttpServletRequest request, HttpServletResponse response) { - try { - return doAuthenticate(request, response); - } catch (BadRequestException e) { - throw AuthenticationException.newBuilder() - .setSource(Source.sso()) - .setMessage(e.getMessage()) - .build(); - } - } - - private Optional<UserDto> doAuthenticate(HttpServletRequest request, HttpServletResponse response) { - if (!enabled) { - return Optional.empty(); - } - Map<String, String> headerValuesByNames = getHeaders(request); - String login = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_LOGIN_HEADER.getKey()); - if (login == null) { - return Optional.empty(); - } - Optional<UserDto> user = getUserFromToken(request, response); - if (user.isPresent() && login.equals(user.get().getLogin())) { - return user; - } - - UserDto userDto = doAuthenticate(headerValuesByNames, login); - jwtHttpHandler.generateToken(userDto, ImmutableMap.of(LAST_REFRESH_TIME_TOKEN_PARAM, system2.now()), request, response); - authenticationEvent.loginSuccess(request, userDto.getLogin(), Source.sso()); - return Optional.of(userDto); - } - - private Optional<UserDto> getUserFromToken(HttpServletRequest request, HttpServletResponse response) { - Optional<JwtHttpHandler.Token> token = jwtHttpHandler.getToken(request, response); - if (!token.isPresent()) { - return Optional.empty(); - } - Date now = new Date(system2.now()); - int refreshIntervalInMinutes = Integer.parseInt(settingsByKey.get(SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES.getKey())); - Long lastFreshTime = (Long) token.get().getProperties().get(LAST_REFRESH_TIME_TOKEN_PARAM); - if (lastFreshTime == null || now.after(addMinutes(new Date(lastFreshTime), refreshIntervalInMinutes))) { - return Optional.empty(); - } - return Optional.of(token.get().getUserDto()); - } - - private UserDto doAuthenticate(Map<String, String> headerValuesByNames, String login) { - String name = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_NAME_HEADER.getKey()); - String email = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_EMAIL_HEADER.getKey()); - UserIdentity.Builder userIdentityBuilder = UserIdentity.builder() - .setLogin(login) - .setName(name == null ? login : name) - .setEmail(email) - .setProviderLogin(login); - if (hasHeader(headerValuesByNames, SONAR_WEB_SSO_GROUPS_HEADER.getKey())) { - String groupsValue = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_GROUPS_HEADER.getKey()); - userIdentityBuilder.setGroups(groupsValue == null ? Collections.emptySet() : new HashSet<>(COMA_SPLITTER.splitToList(groupsValue))); - } - return userRegistrar.register( - UserRegistration.builder() - .setUserIdentity(userIdentityBuilder.build()) - .setProvider(new SsoIdentityProvider()) - .setSource(Source.sso()) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - } - - @CheckForNull - private String getHeaderValue(Map<String, String> headerValuesByNames, String settingKey) { - return headerValuesByNames.get(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH)); - } - - private static Map<String, String> getHeaders(HttpServletRequest request) { - Map<String, String> headers = new HashMap<>(); - Collections.list(request.getHeaderNames()).forEach(header -> headers.put(header.toLowerCase(Locale.ENGLISH), request.getHeader(header))); - return headers; - } - - private boolean hasHeader(Map<String, String> headerValuesByNames, String settingKey) { - return headerValuesByNames.keySet().contains(settingsByKey.get(settingKey).toLowerCase(Locale.ENGLISH)); - } - - private static class SsoIdentityProvider implements IdentityProvider { - @Override - public String getKey() { - return SQ_AUTHORITY; - } - - @Override - public String getName() { - return getKey(); - } - - @Override - public Display getDisplay() { - return null; - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public boolean allowsUsersToSignUp() { - return true; - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/IdentityProviderRepository.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/IdentityProviderRepository.java deleted file mode 100644 index c1484f905c8..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/IdentityProviderRepository.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.Ordering; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.api.server.authentication.IdentityProvider; - -import static com.google.common.collect.FluentIterable.from; - -public class IdentityProviderRepository { - - protected final Map<String, IdentityProvider> providersByKey = new HashMap<>(); - - public IdentityProviderRepository(List<IdentityProvider> identityProviders) { - this.providersByKey.putAll(FluentIterable.from(identityProviders).uniqueIndex(ToKey.INSTANCE)); - } - - /** - * Used by pico when no identity provider available - */ - public IdentityProviderRepository() { - this.providersByKey.clear(); - } - - public IdentityProvider getEnabledByKey(String key) { - IdentityProvider identityProvider = providersByKey.get(key); - if (identityProvider != null && IsEnabledFilter.INSTANCE.apply(identityProvider)) { - return identityProvider; - } - throw new IllegalArgumentException(String.format("Identity provider %s does not exist or is not enabled", key)); - } - - public List<IdentityProvider> getAllEnabledAndSorted() { - return from(providersByKey.values()) - .filter(IsEnabledFilter.INSTANCE) - .toSortedList( - Ordering.natural().onResultOf(ToName.INSTANCE) - ); - } - - private enum IsEnabledFilter implements Predicate<IdentityProvider> { - INSTANCE; - - @Override - public boolean apply(@Nonnull IdentityProvider input) { - return input.isEnabled(); - } - } - - private enum ToKey implements Function<IdentityProvider, String> { - INSTANCE; - - @Override - public String apply(@Nonnull IdentityProvider input) { - return input.getKey(); - } - } - - private enum ToName implements Function<IdentityProvider, String> { - INSTANCE; - - @Override - public String apply(@Nonnull IdentityProvider input) { - return input.getName(); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/InitFilter.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/InitFilter.java deleted file mode 100644 index 5b90a220d67..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/InitFilter.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.server.authentication.BaseIdentityProvider; -import org.sonar.api.server.authentication.IdentityProvider; -import org.sonar.api.server.authentication.OAuth2IdentityProvider; -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 { - - private static final String INIT_CONTEXT = "/sessions/init/"; - - private final BaseContextFactory baseContextFactory; - private final OAuth2ContextFactory oAuth2ContextFactory; - private final AuthenticationEvent authenticationEvent; - private final OAuth2AuthenticationParameters oAuthOAuth2AuthenticationParameters; - - public InitFilter(IdentityProviderRepository identityProviderRepository, BaseContextFactory baseContextFactory, - OAuth2ContextFactory oAuth2ContextFactory, AuthenticationEvent authenticationEvent, OAuth2AuthenticationParameters oAuthOAuth2AuthenticationParameters) { - super(identityProviderRepository); - this.baseContextFactory = baseContextFactory; - this.oAuth2ContextFactory = oAuth2ContextFactory; - this.authenticationEvent = authenticationEvent; - this.oAuthOAuth2AuthenticationParameters = oAuthOAuth2AuthenticationParameters; - } - - @Override - public UrlPattern doGetPattern() { - return UrlPattern.create(INIT_CONTEXT + "*"); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { - HttpServletRequest httpRequest = (HttpServletRequest) request; - HttpServletResponse httpResponse = (HttpServletResponse) response; - - IdentityProvider provider = resolveProviderOrHandleResponse(httpRequest, httpResponse, INIT_CONTEXT); - if (provider != null) { - handleProvider(httpRequest, httpResponse, provider); - } - } - - private void handleProvider(HttpServletRequest request, HttpServletResponse response, IdentityProvider provider) { - try { - if (provider instanceof BaseIdentityProvider) { - handleBaseIdentityProvider(request, response, (BaseIdentityProvider) provider); - } else if (provider instanceof OAuth2IdentityProvider) { - oAuthOAuth2AuthenticationParameters.init(request, response); - handleOAuth2IdentityProvider(request, response, (OAuth2IdentityProvider) provider); - } 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); - handleAuthenticationError(e, request, response); - } catch (Exception e) { - oAuthOAuth2AuthenticationParameters.delete(request, response); - handleError(e, request, response, format("Fail to initialize authentication with provider '%s'", provider.getKey())); - } - } - - private void handleBaseIdentityProvider(HttpServletRequest request, HttpServletResponse response, BaseIdentityProvider provider) { - try { - provider.init(baseContextFactory.newContext(request, response, provider)); - } catch (UnauthorizedException e) { - throw AuthenticationException.newBuilder() - .setSource(Source.external(provider)) - .setMessage(e.getMessage()) - .setPublicMessage(e.getMessage()) - .build(); - } - } - - private void handleOAuth2IdentityProvider(HttpServletRequest request, HttpServletResponse response, OAuth2IdentityProvider provider) { - try { - provider.init(oAuth2ContextFactory.newContext(request, response, provider)); - } catch (UnauthorizedException e) { - throw AuthenticationException.newBuilder() - .setSource(Source.oauth2(provider)) - .setMessage(e.getMessage()) - .setPublicMessage(e.getMessage()) - .build(); - } - } - - @Override - public void init(FilterConfig filterConfig) { - // Nothing to do - } - - @Override - public void destroy() { - // Nothing to do - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtCsrfVerifier.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtCsrfVerifier.java deleted file mode 100644 index f94d1673364..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtCsrfVerifier.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.collect.ImmutableSet; -import java.math.BigInteger; -import java.security.SecureRandom; -import java.util.Set; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.apache.commons.lang.StringUtils; -import org.sonar.server.authentication.event.AuthenticationException; - -import static org.apache.commons.lang.StringUtils.isBlank; -import static org.sonar.server.authentication.Cookies.newCookieBuilder; -import static org.sonar.server.authentication.event.AuthenticationEvent.Method; -import static org.sonar.server.authentication.event.AuthenticationEvent.Source; - -public class JwtCsrfVerifier { - - private static final String CSRF_STATE_COOKIE = "XSRF-TOKEN"; - private static final String CSRF_HEADER = "X-XSRF-TOKEN"; - - private static final Set<String> UPDATE_METHODS = ImmutableSet.of("POST", "PUT", "DELETE"); - private static final String API_URL = "/api"; - - public String generateState(HttpServletRequest request, HttpServletResponse response, int timeoutInSeconds) { - // Create a state token to prevent request forgery. - // Store it in the cookie for later validation. - String state = new BigInteger(130, new SecureRandom()).toString(32); - response.addCookie(newCookieBuilder(request).setName(CSRF_STATE_COOKIE).setValue(state).setHttpOnly(false).setExpiry(timeoutInSeconds).build()); - return state; - } - - public void verifyState(HttpServletRequest request, @Nullable String csrfState, @Nullable String login) { - if (!shouldRequestBeChecked(request)) { - return; - } - - String failureCause = checkCsrf(csrfState, request.getHeader(CSRF_HEADER)); - if (failureCause != null) { - throw AuthenticationException.newBuilder() - .setSource(Source.local(Method.JWT)) - .setLogin(login) - .setMessage(failureCause) - .build(); - } - } - - @CheckForNull - private static String checkCsrf(@Nullable String csrfState, @Nullable String stateInHeader) { - if (isBlank(csrfState)) { - return "Missing reference CSRF value"; - } - if (!StringUtils.equals(csrfState, stateInHeader)) { - return "Wrong CSFR in request"; - } - return null; - } - - public void refreshState(HttpServletRequest request, HttpServletResponse response, String csrfState, int timeoutInSeconds) { - response.addCookie(newCookieBuilder(request).setName(CSRF_STATE_COOKIE).setValue(csrfState).setHttpOnly(false).setExpiry(timeoutInSeconds).build()); - } - - public void removeState(HttpServletRequest request, HttpServletResponse response) { - response.addCookie(newCookieBuilder(request).setName(CSRF_STATE_COOKIE).setValue(null).setHttpOnly(false).setExpiry(0).build()); - } - - private static boolean shouldRequestBeChecked(HttpServletRequest request) { - if (UPDATE_METHODS.contains(request.getMethod())) { - String path = request.getRequestURI().replaceFirst(request.getContextPath(), ""); - return path.startsWith(API_URL); - } - return false; - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtHttpHandler.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtHttpHandler.java deleted file mode 100644 index b961187789f..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtHttpHandler.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.collect.ImmutableMap; -import io.jsonwebtoken.Claims; -import java.util.Collections; -import java.util.Date; -import java.util.Map; -import java.util.Optional; -import javax.annotation.Nullable; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.config.Configuration; -import org.sonar.api.server.ServerSide; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.user.UserDto; - -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Objects.requireNonNull; -import static org.apache.commons.lang.StringUtils.isEmpty; -import static org.apache.commons.lang.time.DateUtils.addSeconds; -import static org.sonar.server.authentication.Cookies.findCookie; -import static org.sonar.server.authentication.Cookies.newCookieBuilder; - -@ServerSide -public class JwtHttpHandler { - - private static final String SESSION_TIMEOUT_IN_MINUTES_PROPERTY = "sonar.web.sessionTimeoutInMinutes"; - private static final int SESSION_TIMEOUT_DEFAULT_VALUE_IN_MINUTES = 3 * 24 * 60; - private static final int MAX_SESSION_TIMEOUT_IN_MINUTES = 3 * 30 * 24 * 60; - - private static final String JWT_COOKIE = "JWT-SESSION"; - private static final String LAST_REFRESH_TIME_PARAM = "lastRefreshTime"; - - private static final String CSRF_JWT_PARAM = "xsrfToken"; - - // Time after which a user will be disconnected - private static final int SESSION_DISCONNECT_IN_SECONDS = 3 * 30 * 24 * 60 * 60; - - // This refresh time is used to refresh the session - // The value must be lower than sessionTimeoutInSeconds - private static final int SESSION_REFRESH_IN_SECONDS = 5 * 60; - - private final System2 system2; - private final DbClient dbClient; - private final JwtSerializer jwtSerializer; - - // This timeout is used to disconnect the user we he has not browse any page for a while - private final int sessionTimeoutInSeconds; - private final JwtCsrfVerifier jwtCsrfVerifier; - - public JwtHttpHandler(System2 system2, DbClient dbClient, Configuration config, JwtSerializer jwtSerializer, JwtCsrfVerifier jwtCsrfVerifier) { - this.jwtSerializer = jwtSerializer; - this.dbClient = dbClient; - this.system2 = system2; - this.sessionTimeoutInSeconds = getSessionTimeoutInSeconds(config); - this.jwtCsrfVerifier = jwtCsrfVerifier; - } - - public void generateToken(UserDto user, Map<String, Object> properties, HttpServletRequest request, HttpServletResponse response) { - String csrfState = jwtCsrfVerifier.generateState(request, response, sessionTimeoutInSeconds); - - String token = jwtSerializer.encode(new JwtSerializer.JwtSession( - user.getUuid(), - sessionTimeoutInSeconds, - ImmutableMap.<String, Object>builder() - .putAll(properties) - .put(LAST_REFRESH_TIME_PARAM, system2.now()) - .put(CSRF_JWT_PARAM, csrfState) - .build())); - response.addCookie(createCookie(request, JWT_COOKIE, token, sessionTimeoutInSeconds)); - } - - public void generateToken(UserDto user, HttpServletRequest request, HttpServletResponse response) { - generateToken(user, Collections.emptyMap(), request, response); - } - - public Optional<UserDto> validateToken(HttpServletRequest request, HttpServletResponse response) { - Optional<Token> token = getToken(request, response); - if (token.isPresent()) { - return Optional.of(token.get().getUserDto()); - } - return Optional.empty(); - } - - public Optional<Token> getToken(HttpServletRequest request, HttpServletResponse response) { - Optional<String> encodedToken = getTokenFromCookie(request); - if (!encodedToken.isPresent()) { - return Optional.empty(); - } - return validateToken(encodedToken.get(), request, response); - } - - private static Optional<String> getTokenFromCookie(HttpServletRequest request) { - Optional<Cookie> jwtCookie = findCookie(JWT_COOKIE, request); - if (!jwtCookie.isPresent()) { - return Optional.empty(); - } - Cookie cookie = jwtCookie.get(); - String token = cookie.getValue(); - if (isEmpty(token)) { - return Optional.empty(); - } - return Optional.of(token); - } - - private Optional<Token> validateToken(String tokenEncoded, HttpServletRequest request, HttpServletResponse response) { - Optional<Claims> claims = jwtSerializer.decode(tokenEncoded); - if (!claims.isPresent()) { - return Optional.empty(); - } - - Date now = new Date(system2.now()); - Claims token = claims.get(); - if (now.after(addSeconds(token.getIssuedAt(), SESSION_DISCONNECT_IN_SECONDS))) { - return Optional.empty(); - } - jwtCsrfVerifier.verifyState(request, (String) token.get(CSRF_JWT_PARAM), token.getSubject()); - - if (now.after(addSeconds(getLastRefreshDate(token), SESSION_REFRESH_IN_SECONDS))) { - refreshToken(token, request, response); - } - - Optional<UserDto> user = selectUserFromUuid(token.getSubject()); - return user.map(userDto -> new Token(userDto, claims.get())); - } - - private static Date getLastRefreshDate(Claims token) { - Long lastFreshTime = (Long) token.get(LAST_REFRESH_TIME_PARAM); - requireNonNull(lastFreshTime, "last refresh time is missing in token"); - return new Date(lastFreshTime); - } - - private void refreshToken(Claims token, HttpServletRequest request, HttpServletResponse response) { - String refreshToken = jwtSerializer.refresh(token, sessionTimeoutInSeconds); - response.addCookie(createCookie(request, JWT_COOKIE, refreshToken, sessionTimeoutInSeconds)); - jwtCsrfVerifier.refreshState(request, response, (String) token.get(CSRF_JWT_PARAM), sessionTimeoutInSeconds); - } - - public void removeToken(HttpServletRequest request, HttpServletResponse response) { - response.addCookie(createCookie(request, JWT_COOKIE, null, 0)); - jwtCsrfVerifier.removeState(request, response); - } - - private static Cookie createCookie(HttpServletRequest request, String name, @Nullable String value, int expirationInSeconds) { - return newCookieBuilder(request).setName(name).setValue(value).setHttpOnly(true).setExpiry(expirationInSeconds).build(); - } - - private Optional<UserDto> selectUserFromUuid(String userUuid) { - try (DbSession dbSession = dbClient.openSession(false)) { - UserDto user = dbClient.userDao().selectByUuid(dbSession, userUuid); - return Optional.ofNullable(user != null && user.isActive() ? user : null); - } - } - - private static int getSessionTimeoutInSeconds(Configuration config) { - int minutes = config.getInt(SESSION_TIMEOUT_IN_MINUTES_PROPERTY).orElse(SESSION_TIMEOUT_DEFAULT_VALUE_IN_MINUTES); - checkArgument(minutes > 0, "Property %s must be strictly positive. Got %s", SESSION_TIMEOUT_IN_MINUTES_PROPERTY, minutes); - checkArgument(minutes <= MAX_SESSION_TIMEOUT_IN_MINUTES, "Property %s must not be greater than 3 months (%s minutes). Got %s minutes", - SESSION_TIMEOUT_IN_MINUTES_PROPERTY, MAX_SESSION_TIMEOUT_IN_MINUTES, minutes); - return minutes * 60; - } - - public static class Token { - private final UserDto userDto; - private final Map<String, Object> properties; - - public Token(UserDto userDto, Map<String, Object> properties) { - this.userDto = userDto; - this.properties = properties; - } - - public UserDto getUserDto() { - return userDto; - } - - public Map<String, Object> getProperties() { - return properties; - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtSerializer.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtSerializer.java deleted file mode 100644 index 33d5aa3d958..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/JwtSerializer.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.annotations.VisibleForTesting; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.JwtBuilder; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.SignatureException; -import java.util.Base64; -import java.util.Collections; -import java.util.Date; -import java.util.Map; -import java.util.Optional; -import javax.annotation.concurrent.Immutable; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import org.sonar.api.Startable; -import org.sonar.api.config.Configuration; -import org.sonar.api.server.ServerSide; -import org.sonar.api.utils.System2; -import org.sonar.core.util.UuidFactory; -import org.sonar.server.authentication.event.AuthenticationEvent.Source; -import org.sonar.server.authentication.event.AuthenticationException; - -import static com.google.common.base.Preconditions.checkNotNull; -import static io.jsonwebtoken.impl.crypto.MacProvider.generateKey; -import static java.util.Objects.requireNonNull; -import static org.sonar.process.ProcessProperties.Property.AUTH_JWT_SECRET; - -/** - * This class can be used to encode or decode a JWT token - */ -@ServerSide -public class JwtSerializer implements Startable { - - private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256; - - private final Configuration config; - private final System2 system2; - private final UuidFactory uuidFactory; - - private SecretKey secretKey; - - public JwtSerializer(Configuration config, System2 system2, UuidFactory uuidFactory) { - this.config = config; - this.system2 = system2; - this.uuidFactory = uuidFactory; - } - - @VisibleForTesting - SecretKey getSecretKey() { - return secretKey; - } - - @Override - public void start() { - Optional<String> encodedKey = config.get(AUTH_JWT_SECRET.getKey()); - if (encodedKey.isPresent()) { - this.secretKey = decodeSecretKeyProperty(encodedKey.get()); - } else { - this.secretKey = generateSecretKey(); - } - } - - String encode(JwtSession jwtSession) { - checkIsStarted(); - long now = system2.now(); - JwtBuilder jwtBuilder = Jwts.builder() - .setId(uuidFactory.create()) - .setSubject(jwtSession.getUserLogin()) - .setIssuedAt(new Date(now)) - .setExpiration(new Date(now + jwtSession.getExpirationTimeInSeconds() * 1000)) - .signWith(SIGNATURE_ALGORITHM, secretKey); - for (Map.Entry<String, Object> entry : jwtSession.getProperties().entrySet()) { - jwtBuilder.claim(entry.getKey(), entry.getValue()); - } - return jwtBuilder.compact(); - } - - Optional<Claims> decode(String token) { - checkIsStarted(); - Claims claims = null; - try { - claims = Jwts.parser() - .setSigningKey(secretKey) - .parseClaimsJws(token) - .getBody(); - requireNonNull(claims.getId(), "Token id hasn't been found"); - requireNonNull(claims.getSubject(), "Token subject hasn't been found"); - requireNonNull(claims.getExpiration(), "Token expiration date hasn't been found"); - requireNonNull(claims.getIssuedAt(), "Token creation date hasn't been found"); - return Optional.of(claims); - } catch (ExpiredJwtException | SignatureException e) { - return Optional.empty(); - } catch (Exception e) { - throw AuthenticationException.newBuilder() - .setSource(Source.jwt()) - .setLogin(claims == null ? null : claims.getSubject()) - .setMessage(e.getMessage()) - .build(); - } - } - - String refresh(Claims token, int expirationTimeInSeconds) { - checkIsStarted(); - long now = system2.now(); - JwtBuilder jwtBuilder = Jwts.builder(); - for (Map.Entry<String, Object> entry : token.entrySet()) { - jwtBuilder.claim(entry.getKey(), entry.getValue()); - } - jwtBuilder.setExpiration(new Date(now + expirationTimeInSeconds * 1_000L)) - .signWith(SIGNATURE_ALGORITHM, secretKey); - return jwtBuilder.compact(); - } - - private static SecretKey generateSecretKey() { - return generateKey(SIGNATURE_ALGORITHM); - } - - private static SecretKey decodeSecretKeyProperty(String base64SecretKey) { - byte[] decodedKey = Base64.getDecoder().decode(base64SecretKey); - return new SecretKeySpec(decodedKey, 0, decodedKey.length, SIGNATURE_ALGORITHM.getJcaName()); - } - - private void checkIsStarted() { - checkNotNull(secretKey, "%s not started", getClass().getName()); - } - - @Override - public void stop() { - secretKey = null; - } - - @Immutable - static class JwtSession { - - private final String userLogin; - private final long expirationTimeInSeconds; - private final Map<String, Object> properties; - - JwtSession(String userLogin, long expirationTimeInSeconds) { - this(userLogin, expirationTimeInSeconds, Collections.emptyMap()); - } - - JwtSession(String userLogin, long expirationTimeInSeconds, Map<String, Object> properties) { - this.userLogin = requireNonNull(userLogin, "User login cannot be null"); - this.expirationTimeInSeconds = expirationTimeInSeconds; - this.properties = properties; - } - - String getUserLogin() { - return userLogin; - } - - long getExpirationTimeInSeconds() { - return expirationTimeInSeconds; - } - - Map<String, Object> getProperties() { - return properties; - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/LogOAuthWarning.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/LogOAuthWarning.java deleted file mode 100644 index fbc751cc034..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/LogOAuthWarning.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.apache.commons.lang.StringUtils; -import org.sonar.api.Startable; -import org.sonar.api.platform.Server; -import org.sonar.api.server.authentication.OAuth2IdentityProvider; -import org.sonar.api.utils.log.Loggers; - -public class LogOAuthWarning implements Startable { - - private final Server server; - private final OAuth2IdentityProvider[] providers; - - public LogOAuthWarning(Server server, OAuth2IdentityProvider[] providers) { - this.server = server; - this.providers = providers; - } - - /** - * Used by default by picocontainer when no OAuth2IdentityProvider are present - */ - public LogOAuthWarning(Server server) { - this(server, new OAuth2IdentityProvider[0]); - } - - @Override - public void start() { - if (providers.length == 0) { - return; - } - String publicRootUrl = server.getPublicRootUrl(); - if (StringUtils.startsWithIgnoreCase(publicRootUrl, "http:")) { - Loggers.get(getClass()).warn( - "For security reasons, OAuth authentication should use HTTPS. You should set the property 'Administration > Configuration > Server base URL' to a HTTPS URL."); - } - } - - @Override - public void stop() { - // nothing to do - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2AuthenticationParameters.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2AuthenticationParameters.java deleted file mode 100644 index 45c314c21de..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2AuthenticationParameters.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Optional; -import javax.servlet.FilterConfig; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.server.authentication.OAuth2IdentityProvider; - -/** - * This class is used to store some parameters during the OAuth2 authentication process, by using a cookie. - * - * Parameters are read from the request during {@link InitFilter#init(FilterConfig)} - * and reset when {@link OAuth2IdentityProvider.CallbackContext#redirectToRequestedPage()} is called. - */ -public interface OAuth2AuthenticationParameters { - - void init(HttpServletRequest request, HttpServletResponse response); - - Optional<String> getReturnTo(HttpServletRequest request); - - Optional<Boolean> getAllowEmailShift(HttpServletRequest request); - - Optional<Boolean> getAllowUpdateLogin(HttpServletRequest request); - - void delete(HttpServletRequest request, HttpServletResponse response); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImpl.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImpl.java deleted file mode 100644 index debc6a677f1..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImpl.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.base.Strings; -import com.google.common.reflect.TypeToken; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -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; - -public class OAuth2AuthenticationParametersImpl implements OAuth2AuthenticationParameters { - - private static final String AUTHENTICATION_COOKIE_NAME = "AUTH-PARAMS"; - private static final int FIVE_MINUTES_IN_SECONDS = 5 * 60; - - /** - * The HTTP parameter that contains the path where the user should be redirect to. - * Please note that the web context is included. - */ - 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 - */ - private static final String ALLOW_LOGIN_UPDATE_PARAMETER = "allowUpdateLogin"; - - private static final Type JSON_MAP_TYPE = new TypeToken<HashMap<String, String>>() { - }.getType(); - - @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; - } - response.addCookie(newCookieBuilder(request) - .setName(AUTHENTICATION_COOKIE_NAME) - .setValue(toJson(parameters)) - .setHttpOnly(true) - .setExpiry(FIVE_MINUTES_IN_SECONDS) - .build()); - } - - @Override - public Optional<String> getReturnTo(HttpServletRequest request) { - return getParameter(request, RETURN_TO_PARAMETER); - } - - @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()) { - return empty(); - } - - Map<String, String> parameters = fromJson(cookie.get().getValue()); - if (parameters.isEmpty()) { - return empty(); - } - return Optional.ofNullable(parameters.get(parameterKey)); - } - - @Override - public void delete(HttpServletRequest request, HttpServletResponse response) { - response.addCookie(newCookieBuilder(request) - .setName(AUTHENTICATION_COOKIE_NAME) - .setValue(null) - .setHttpOnly(true) - .setExpiry(0) - .build()); - } - - private static String toJson(Map<String, String> map) { - Gson gson = new GsonBuilder().create(); - return encodeMessage(gson.toJson(map)); - } - - private static Map<String, String> fromJson(String json) { - Gson gson = new GsonBuilder().create(); - try { - return gson.fromJson(decode(json, UTF_8.name()), JSON_MAP_TYPE); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); - } - } - - /** - * This sanitization has been inspired by 'IsUrlLocalToHost()' method in - * https://docs.microsoft.com/en-us/aspnet/mvc/overview/security/preventing-open-redirection-attacks - */ - private static Optional<String> sanitizeRedirectUrl(@Nullable String url) { - if (Strings.isNullOrEmpty(url)) { - return empty(); - } - if (url.startsWith("//") || url.startsWith("/\\")) { - return empty(); - } - if (!url.startsWith("/")) { - return empty(); - } - return Optional.of(url); - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackContext.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackContext.java deleted file mode 100644 index 1a0413b3fcf..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackContext.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Set; -import javax.annotation.Nullable; -import org.sonar.api.server.authentication.OAuth2IdentityProvider; -import org.sonar.api.server.authentication.UserIdentity; - -public interface OAuth2CallbackContext extends OAuth2IdentityProvider.CallbackContext { - - void authenticate(UserIdentity userIdentity, @Nullable Set<String> organizationAlmIds); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackFilter.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackFilter.java deleted file mode 100644 index eba7efbdabd..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2CallbackFilter.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.server.authentication.IdentityProvider; -import org.sonar.api.server.authentication.OAuth2IdentityProvider; -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 { - - private final OAuth2ContextFactory oAuth2ContextFactory; - private final AuthenticationEvent authenticationEvent; - private final OAuth2AuthenticationParameters oauth2Parameters; - private final ThreadLocalUserSession threadLocalUserSession; - - public OAuth2CallbackFilter(IdentityProviderRepository identityProviderRepository, OAuth2ContextFactory oAuth2ContextFactory, - AuthenticationEvent authenticationEvent, OAuth2AuthenticationParameters oauth2Parameters, ThreadLocalUserSession threadLocalUserSession) { - super(identityProviderRepository); - this.oAuth2ContextFactory = oAuth2ContextFactory; - this.authenticationEvent = authenticationEvent; - this.oauth2Parameters = oauth2Parameters; - this.threadLocalUserSession = threadLocalUserSession; - } - - @Override - public UrlPattern doGetPattern() { - return UrlPattern.create(CALLBACK_PATH + "*"); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { - HttpServletRequest httpRequest = (HttpServletRequest) request; - HttpServletResponse httpResponse = (HttpServletResponse) response; - - IdentityProvider provider = resolveProviderOrHandleResponse(httpRequest, httpResponse, CALLBACK_PATH); - if (provider != null) { - handleProvider(httpRequest, (HttpServletResponse) response, provider); - } - } - - private void handleProvider(HttpServletRequest request, HttpServletResponse response, IdentityProvider provider) { - try { - if (provider instanceof OAuth2IdentityProvider) { - handleOAuth2Provider(response, request, (OAuth2IdentityProvider) provider); - } 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); - handleAuthenticationError(e, request, response); - } catch (Exception e) { - oauth2Parameters.delete(request, response); - handleError(e, request, response, format("Fail to callback authentication with '%s'", provider.getKey())); - } - } - - private void handleOAuth2Provider(HttpServletResponse response, HttpServletRequest httpRequest, OAuth2IdentityProvider oAuth2Provider) { - OAuth2IdentityProvider.CallbackContext context = oAuth2ContextFactory.newCallback(httpRequest, response, oAuth2Provider); - try { - oAuth2Provider.callback(context); - } catch (UnauthorizedException e) { - throw AuthenticationException.newBuilder() - .setSource(Source.oauth2(oAuth2Provider)) - .setMessage(e.getMessage()) - .setPublicMessage(e.getMessage()) - .build(); - } - if (threadLocalUserSession.hasSession()) { - authenticationEvent.loginSuccess(httpRequest, threadLocalUserSession.getLogin(), Source.oauth2(oAuth2Provider)); - } else { - throw AuthenticationException.newBuilder() - .setSource(Source.oauth2(oAuth2Provider)) - .setMessage("Plugin did not call authenticate") - .build(); - } - } - - @Override - public void init(FilterConfig filterConfig) { - // Nothing to do - } - - @Override - public void destroy() { - // Nothing to do - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2ContextFactory.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2ContextFactory.java deleted file mode 100644 index 6d9b3810a1b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2ContextFactory.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.io.IOException; -import java.util.Optional; -import java.util.Set; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.platform.Server; -import org.sonar.api.server.ServerSide; -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; - -import static java.lang.String.format; -import static org.sonar.server.authentication.OAuth2CallbackFilter.CALLBACK_PATH; - -@ServerSide -public class OAuth2ContextFactory { - - private final ThreadLocalUserSession threadLocalUserSession; - private final UserRegistrar userRegistrar; - private final Server server; - private final OAuthCsrfVerifier csrfVerifier; - private final JwtHttpHandler jwtHttpHandler; - private final UserSessionFactory userSessionFactory; - private final OAuth2AuthenticationParameters oAuthParameters; - - public OAuth2ContextFactory(ThreadLocalUserSession threadLocalUserSession, UserRegistrar userRegistrar, Server server, - OAuthCsrfVerifier csrfVerifier, JwtHttpHandler jwtHttpHandler, UserSessionFactory userSessionFactory, OAuth2AuthenticationParameters oAuthParameters) { - this.threadLocalUserSession = threadLocalUserSession; - this.userRegistrar = userRegistrar; - this.server = server; - this.csrfVerifier = csrfVerifier; - this.jwtHttpHandler = jwtHttpHandler; - this.userSessionFactory = userSessionFactory; - this.oAuthParameters = oAuthParameters; - } - - public OAuth2IdentityProvider.InitContext newContext(HttpServletRequest request, HttpServletResponse response, OAuth2IdentityProvider identityProvider) { - return new OAuthContextImpl(request, response, identityProvider); - } - - public OAuth2IdentityProvider.CallbackContext newCallback(HttpServletRequest request, HttpServletResponse response, OAuth2IdentityProvider identityProvider) { - return new OAuthContextImpl(request, response, identityProvider); - } - - public class OAuthContextImpl implements OAuth2IdentityProvider.InitContext, OAuth2CallbackContext { - - private final HttpServletRequest request; - private final HttpServletResponse response; - private final OAuth2IdentityProvider identityProvider; - - public OAuthContextImpl(HttpServletRequest request, HttpServletResponse response, OAuth2IdentityProvider identityProvider) { - this.request = request; - this.response = response; - this.identityProvider = identityProvider; - } - - @Override - public String getCallbackUrl() { - return server.getPublicRootUrl() + CALLBACK_PATH + identityProvider.getKey(); - } - - @Override - public String generateCsrfState() { - return csrfVerifier.generateState(request, response); - } - - @Override - public HttpServletRequest getRequest() { - return request; - } - - @Override - public HttpServletResponse getResponse() { - return response; - } - - @Override - public void redirectTo(String url) { - try { - response.sendRedirect(url); - } catch (IOException e) { - throw new IllegalStateException(format("Fail to redirect to %s", url), e); - } - } - - @Override - public void verifyCsrfState() { - csrfVerifier.verifyState(request, response, identityProvider); - } - - @Override - public void verifyCsrfState(String parameterName) { - csrfVerifier.verifyState(request, response, identityProvider, parameterName); - } - - @Override - public void redirectToRequestedPage() { - try { - Optional<String> redirectTo = oAuthParameters.getReturnTo(request); - oAuthParameters.delete(request, response); - getResponse().sendRedirect(redirectTo.orElse(server.getContextPath() + "/")); - } catch (IOException e) { - throw new IllegalStateException("Fail to redirect to requested page", e); - } - } - - @Override - public void authenticate(UserIdentity userIdentity) { - authenticate(userIdentity, null); - } - - @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); - threadLocalUserSession.set(userSessionFactory.create(userDto)); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuthCsrfVerifier.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuthCsrfVerifier.java deleted file mode 100644 index 0f6c17fc49c..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/OAuthCsrfVerifier.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.math.BigInteger; -import java.security.SecureRandom; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.server.authentication.OAuth2IdentityProvider; -import org.sonar.server.authentication.event.AuthenticationException; - -import static java.lang.String.format; -import static org.apache.commons.codec.digest.DigestUtils.sha256Hex; -import static org.apache.commons.lang.StringUtils.isBlank; -import static org.sonar.server.authentication.Cookies.findCookie; -import static org.sonar.server.authentication.Cookies.newCookieBuilder; -import static org.sonar.server.authentication.event.AuthenticationEvent.Source; - -public class OAuthCsrfVerifier { - - private static final String CSRF_STATE_COOKIE = "OAUTHSTATE"; - private static final String DEFAULT_STATE_PARAMETER_NAME = "state"; - - public String generateState(HttpServletRequest request, HttpServletResponse response) { - // Create a state token to prevent request forgery. - // Store it in the session for later validation. - String state = new BigInteger(130, new SecureRandom()).toString(32); - response.addCookie(newCookieBuilder(request).setName(CSRF_STATE_COOKIE).setValue(sha256Hex(state)).setHttpOnly(true).setExpiry(-1).build()); - return state; - } - - public void verifyState(HttpServletRequest request, HttpServletResponse response, OAuth2IdentityProvider provider) { - verifyState(request, response, provider, DEFAULT_STATE_PARAMETER_NAME); - } - - public void verifyState(HttpServletRequest request, HttpServletResponse response, OAuth2IdentityProvider provider, String parameterName) { - Cookie cookie = findCookie(CSRF_STATE_COOKIE, request) - .orElseThrow(AuthenticationException.newBuilder() - .setSource(Source.oauth2(provider)) - .setMessage(format("Cookie '%s' is missing", CSRF_STATE_COOKIE))::build); - String hashInCookie = cookie.getValue(); - - // remove cookie - response.addCookie(newCookieBuilder(request).setName(CSRF_STATE_COOKIE).setValue(null).setHttpOnly(true).setExpiry(0).build()); - - String stateInRequest = request.getParameter(parameterName); - if (isBlank(stateInRequest) || !sha256Hex(stateInRequest).equals(hashInCookie)) { - throw AuthenticationException.newBuilder() - .setSource(Source.oauth2(provider)) - .setMessage("CSRF state value is invalid") - .build(); - } - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/RequestAuthenticator.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/RequestAuthenticator.java deleted file mode 100644 index 48c4b269bb2..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/RequestAuthenticator.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.server.ServerSide; -import org.sonar.server.user.UserSession; - -@ServerSide -public interface RequestAuthenticator { - - /** - * @throws org.sonar.server.authentication.event.AuthenticationException if user is not authenticated - */ - UserSession authenticate(HttpServletRequest request, HttpServletResponse response); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/RequestAuthenticatorImpl.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/RequestAuthenticatorImpl.java deleted file mode 100644 index 0d5643cc5b9..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/RequestAuthenticatorImpl.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.db.user.UserDto; -import org.sonar.server.user.UserSession; -import org.sonar.server.user.UserSessionFactory; - -public class RequestAuthenticatorImpl implements RequestAuthenticator { - - private final JwtHttpHandler jwtHttpHandler; - private final BasicAuthentication basicAuthentication; - private final HttpHeadersAuthentication httpHeadersAuthentication; - private final UserSessionFactory userSessionFactory; - private final List<CustomAuthentication> customAuthentications; - - public RequestAuthenticatorImpl(JwtHttpHandler jwtHttpHandler, BasicAuthentication basicAuthentication, HttpHeadersAuthentication httpHeadersAuthentication, - UserSessionFactory userSessionFactory, CustomAuthentication[] customAuthentications) { - this.jwtHttpHandler = jwtHttpHandler; - this.basicAuthentication = basicAuthentication; - this.httpHeadersAuthentication = httpHeadersAuthentication; - this.userSessionFactory = userSessionFactory; - this.customAuthentications = Arrays.asList(customAuthentications); - } - - public RequestAuthenticatorImpl(JwtHttpHandler jwtHttpHandler, BasicAuthentication basicAuthentication, HttpHeadersAuthentication httpHeadersAuthentication, - UserSessionFactory userSessionFactory) { - this(jwtHttpHandler, basicAuthentication, httpHeadersAuthentication, userSessionFactory, new CustomAuthentication[0]); - } - - @Override - public UserSession authenticate(HttpServletRequest request, HttpServletResponse response) { - for (CustomAuthentication customAuthentication : customAuthentications) { - Optional<UserSession> session = customAuthentication.authenticate(request, response); - if (session.isPresent()) { - return session.get(); - } - } - - Optional<UserDto> userOpt = loadUser(request, response); - if (userOpt.isPresent()) { - return userSessionFactory.create(userOpt.get()); - } - return userSessionFactory.createAnonymous(); - } - - private Optional<UserDto> loadUser(HttpServletRequest request, HttpServletResponse response) { - // Try first to authenticate from SSO, then JWT token, then try from basic http header - - // SSO authentication should come first in order to update JWT if user from header is not the same is user from JWT - Optional<UserDto> user = httpHeadersAuthentication.authenticate(request, response); - if (user.isPresent()) { - return user; - } - user = jwtHttpHandler.validateToken(request, response); - if (user.isPresent()) { - return user; - } - return basicAuthentication.authenticate(request); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java deleted file mode 100644 index 85828432ad1..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import javax.annotation.CheckForNull; -import javax.annotation.concurrent.Immutable; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.db.user.GroupDto; -import org.sonar.server.user.AbstractUserSession; - -@Immutable -public class SafeModeUserSession extends AbstractUserSession { - - @Override - protected boolean hasPermissionImpl(OrganizationPermission permission, String organizationUuid) { - return false; - } - - @Override - protected Optional<String> componentUuidToProjectUuid(String componentUuid) { - return Optional.empty(); - } - - @Override - protected boolean hasProjectUuidPermission(String permission, String projectUuid) { - return false; - } - - @Override - protected boolean hasMembershipImpl(OrganizationDto organizationDto) { - return false; - } - - @CheckForNull - @Override - public String getLogin() { - return null; - } - - @CheckForNull - @Override - public String getUuid() { - return null; - } - - @CheckForNull - @Override - public String getName() { - return null; - } - - @CheckForNull - @Override - public Integer getUserId() { - return null; - } - - @Override - public Collection<GroupDto> getGroups() { - return Collections.emptyList(); - } - - @Override - public Optional<IdentityProvider> getIdentityProvider() { - return Optional.empty(); - } - - @Override - public Optional<ExternalIdentity> getExternalIdentity() { - return Optional.empty(); - } - - @Override - public boolean isLoggedIn() { - return false; - } - - @Override - public boolean isRoot() { - return false; - } - - @Override - public boolean isSystemAdministrator() { - return false; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdater.java deleted file mode 100644 index aa55e9e1adb..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdater.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserTokenDto; - -public interface UserLastConnectionDatesUpdater { - - void updateLastConnectionDateIfNeeded(UserDto user); - - void updateLastConnectionDateIfNeeded(UserTokenDto userToken); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImpl.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImpl.java deleted file mode 100644 index 87326e6c236..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImpl.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.annotation.Nullable; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserTokenDto; - -public class UserLastConnectionDatesUpdaterImpl implements UserLastConnectionDatesUpdater { - - private static final long ONE_HOUR_IN_MILLISECONDS = 60 * 60 * 1000L; - - private final DbClient dbClient; - private final System2 system2; - - public UserLastConnectionDatesUpdaterImpl(DbClient dbClient, System2 system2) { - this.dbClient = dbClient; - this.system2 = system2; - } - - @Override - public void updateLastConnectionDateIfNeeded(UserDto user) { - Long lastConnectionDate = user.getLastConnectionDate(); - long now = system2.now(); - if (doesNotRequireUpdate(lastConnectionDate, now)) { - return; - } - try (DbSession dbSession = dbClient.openSession(false)) { - dbClient.userDao().update(dbSession, user.setLastConnectionDate(now)); - dbSession.commit(); - } - } - - @Override - public void updateLastConnectionDateIfNeeded(UserTokenDto userToken) { - Long lastConnectionDate = userToken.getLastConnectionDate(); - long now = system2.now(); - if (doesNotRequireUpdate(lastConnectionDate, now)) { - return; - } - try (DbSession dbSession = dbClient.openSession(false)) { - dbClient.userTokenDao().update(dbSession, userToken.setLastConnectionDate(now)); - userToken.setLastConnectionDate(now); - dbSession.commit(); - } - } - - private static boolean doesNotRequireUpdate(@Nullable Long lastConnectionDate, long now) { - // Update date only once per hour in order to decrease pressure on DB - return lastConnectionDate != null && (now - lastConnectionDate) < ONE_HOUR_IN_MILLISECONDS; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrar.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrar.java deleted file mode 100644 index e398d2249b2..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrar.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.sonar.db.user.UserDto; - -public interface UserRegistrar { - - UserDto register(UserRegistration registration); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java deleted file mode 100644 index 6f408e3c7fc..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.collect.Sets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -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.api.utils.log.Loggers; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.alm.ALM; -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.AuthenticationException; -import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException; -import org.sonar.server.organization.DefaultOrganization; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.MemberUpdater; -import org.sonar.server.organization.OrganizationFlags; -import org.sonar.server.user.ExternalIdentity; -import org.sonar.server.user.NewUser; -import org.sonar.server.user.UpdateUser; -import org.sonar.server.user.UserSession; -import org.sonar.server.user.UserUpdater; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; - -public class UserRegistrarImpl implements UserRegistrar { - - private static final Logger LOGGER = Loggers.get(UserRegistrarImpl.class); - - private final DbClient dbClient; - private final UserUpdater userUpdater; - private final DefaultOrganizationProvider defaultOrganizationProvider; - private final OrganizationFlags organizationFlags; - private final DefaultGroupFinder defaultGroupFinder; - private final MemberUpdater memberUpdater; - - public UserRegistrarImpl(DbClient dbClient, UserUpdater userUpdater, DefaultOrganizationProvider defaultOrganizationProvider, OrganizationFlags organizationFlags, - DefaultGroupFinder defaultGroupFinder, MemberUpdater memberUpdater) { - this.dbClient = dbClient; - this.userUpdater = userUpdater; - this.defaultOrganizationProvider = defaultOrganizationProvider; - this.organizationFlags = organizationFlags; - this.defaultGroupFinder = defaultGroupFinder; - this.memberUpdater = memberUpdater; - } - - @Override - public UserDto register(UserRegistration registration) { - try (DbSession dbSession = dbClient.openSession(false)) { - UserDto userDto = getUser(dbSession, registration.getUserIdentity(), registration.getProvider()); - if (userDto == null) { - return registerNewUser(dbSession, null, registration); - } - if (!userDto.isActive()) { - return registerNewUser(dbSession, userDto, registration); - } - return registerExistingUser(dbSession, userDto, registration); - } - } - - @CheckForNull - private UserDto getUser(DbSession dbSession, UserIdentity userIdentity, IdentityProvider provider) { - // First, try to authenticate using the external ID - UserDto user = dbClient.userDao().selectByExternalIdAndIdentityProvider(dbSession, getProviderIdOrProviderLogin(userIdentity), provider.getKey()); - if (user != null) { - return user; - } - // Then, try with the external login, for instance when external ID has changed - user = dbClient.userDao().selectByExternalLoginAndIdentityProvider(dbSession, userIdentity.getProviderLogin(), provider.getKey()); - if (user != null) { - return user; - } - // Last, try with login, for instance when external ID and external login has been updated - String login = userIdentity.getLogin(); - if (login == null) { - return null; - } - return dbClient.userDao().selectByLogin(dbSession, login); - } - - private UserDto registerNewUser(DbSession dbSession, @Nullable UserDto disabledUser, UserRegistration authenticatorParameters) { - Optional<UserDto> otherUserToIndex = detectEmailUpdate(dbSession, authenticatorParameters); - NewUser newUser = createNewUser(authenticatorParameters); - if (disabledUser == null) { - return userUpdater.createAndCommit(dbSession, newUser, beforeCommit(dbSession, true, authenticatorParameters), toArray(otherUserToIndex)); - } - return userUpdater.reactivateAndCommit(dbSession, disabledUser, newUser, beforeCommit(dbSession, true, authenticatorParameters), toArray(otherUserToIndex)); - } - - private UserDto registerExistingUser(DbSession dbSession, UserDto userDto, UserRegistration authenticatorParameters) { - UpdateUser update = new UpdateUser() - .setEmail(authenticatorParameters.getUserIdentity().getEmail()) - .setName(authenticatorParameters.getUserIdentity().getName()) - .setExternalIdentity(new ExternalIdentity( - authenticatorParameters.getProvider().getKey(), - authenticatorParameters.getUserIdentity().getProviderLogin(), - authenticatorParameters.getUserIdentity().getProviderId())); - String login = authenticatorParameters.getUserIdentity().getLogin(); - if (login != null) { - update.setLogin(login); - } - Optional<UserDto> otherUserToIndex = detectEmailUpdate(dbSession, authenticatorParameters); - userUpdater.updateAndCommit(dbSession, userDto, update, beforeCommit(dbSession, false, authenticatorParameters), toArray(otherUserToIndex)); - return userDto; - } - - private Consumer<UserDto> beforeCommit(DbSession dbSession, boolean isNewUser, UserRegistration authenticatorParameters) { - return user -> { - syncGroups(dbSession, authenticatorParameters.getUserIdentity(), user); - synchronizeOrganizationMembership(dbSession, user, authenticatorParameters, isNewUser); - }; - } - - private Optional<UserDto> detectEmailUpdate(DbSession dbSession, UserRegistration authenticatorParameters) { - String email = authenticatorParameters.getUserIdentity().getEmail(); - if (email == null) { - return Optional.empty(); - } - List<UserDto> existingUsers = dbClient.userDao().selectByEmail(dbSession, email); - if (existingUsers.isEmpty()) { - return Optional.empty(); - } - if (existingUsers.size() > 1) { - throw generateExistingEmailError(authenticatorParameters, email); - } - - UserDto existingUser = existingUsers.get(0); - if (existingUser == null || isSameUser(existingUser, authenticatorParameters)) { - 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)); - } - } - - private static boolean isSameUser(UserDto existingUser, UserRegistration authenticatorParameters) { - // Check if same login - return Objects.equals(existingUser.getLogin(), authenticatorParameters.getUserIdentity().getLogin()) || - // Check if same identity provider and same provider id or provider login - (Objects.equals(existingUser.getExternalIdentityProvider(), authenticatorParameters.getProvider().getKey()) && - (Objects.equals(existingUser.getExternalId(), getProviderIdOrProviderLogin(authenticatorParameters.getUserIdentity())) - || Objects.equals(existingUser.getExternalLogin(), authenticatorParameters.getUserIdentity().getProviderLogin()))); - } - - private void syncGroups(DbSession dbSession, UserIdentity userIdentity, UserDto userDto) { - if (!userIdentity.shouldSyncGroups()) { - return; - } - String userLogin = userDto.getLogin(); - Set<String> userGroups = new HashSet<>(dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, singletonList(userLogin)).get(userLogin)); - Set<String> identityGroups = userIdentity.getGroups(); - LOGGER.debug("List of groups returned by the identity provider '{}'", identityGroups); - - Collection<String> groupsToAdd = Sets.difference(identityGroups, userGroups); - Collection<String> groupsToRemove = Sets.difference(userGroups, identityGroups); - Collection<String> allGroups = new ArrayList<>(groupsToAdd); - allGroups.addAll(groupsToRemove); - DefaultOrganization defaultOrganization = defaultOrganizationProvider.get(); - Map<String, GroupDto> groupsByName = dbClient.groupDao().selectByNames(dbSession, defaultOrganization.getUuid(), allGroups) - .stream() - .collect(uniqueIndex(GroupDto::getName)); - - addGroups(dbSession, userDto, groupsToAdd, groupsByName); - removeGroups(dbSession, userDto, groupsToRemove, groupsByName); - } - - private void addGroups(DbSession dbSession, UserDto userDto, Collection<String> groupsToAdd, Map<String, GroupDto> groupsByName) { - groupsToAdd.stream().map(groupsByName::get).filter(Objects::nonNull).forEach( - groupDto -> { - LOGGER.debug("Adding group '{}' to user '{}'", groupDto.getName(), userDto.getLogin()); - dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupId(groupDto.getId()).setUserId(userDto.getId())); - }); - } - - private void removeGroups(DbSession dbSession, UserDto userDto, Collection<String> groupsToRemove, Map<String, GroupDto> groupsByName) { - Optional<GroupDto> defaultGroup = getDefaultGroup(dbSession); - groupsToRemove.stream().map(groupsByName::get) - .filter(Objects::nonNull) - // user should be member of default group only when organizations are disabled, as the IdentityProvider API doesn't handle yet - // organizations - .filter(group -> !defaultGroup.isPresent() || !group.getId().equals(defaultGroup.get().getId())) - .forEach(groupDto -> { - LOGGER.debug("Removing group '{}' from user '{}'", groupDto.getName(), userDto.getLogin()); - dbClient.userGroupDao().delete(dbSession, groupDto.getId(), userDto.getId()); - }); - } - - private Optional<GroupDto> getDefaultGroup(DbSession dbSession) { - return organizationFlags.isEnabled(dbSession) ? Optional.empty() : Optional.of(defaultGroupFinder.findDefaultGroup(dbSession, defaultOrganizationProvider.get().getUuid())); - } - - private void synchronizeOrganizationMembership(DbSession dbSession, UserDto userDto, UserRegistration authenticatorParameters, boolean isNewUser) { - Set<String> almOrganizationIds = authenticatorParameters.getOrganizationAlmIds(); - if (almOrganizationIds == null || !isNewUser || !organizationFlags.isEnabled(dbSession)) { - return; - } - UserSession.IdentityProvider identityProvider = UserSession.IdentityProvider.getFromKey(authenticatorParameters.getProvider().getKey()); - if (identityProvider != UserSession.IdentityProvider.GITHUB) { - return; - } - memberUpdater.synchronizeUserOrganizationMembership(dbSession, userDto, ALM.GITHUB, almOrganizationIds); - } - - private static NewUser createNewUser(UserRegistration authenticatorParameters) { - String identityProviderKey = authenticatorParameters.getProvider().getKey(); - if (!authenticatorParameters.getProvider().allowsUsersToSignUp()) { - throw AuthenticationException.newBuilder() - .setSource(authenticatorParameters.getSource()) - .setLogin(authenticatorParameters.getUserIdentity().getProviderLogin()) - .setMessage(format("User signup disabled for provider '%s'", identityProviderKey)) - .setPublicMessage(format("'%s' users are not allowed to sign up", identityProviderKey)) - .build(); - } - return NewUser.builder() - .setLogin(authenticatorParameters.getUserIdentity().getLogin()) - .setEmail(authenticatorParameters.getUserIdentity().getEmail()) - .setName(authenticatorParameters.getUserIdentity().getName()) - .setExternalIdentity( - new ExternalIdentity( - identityProviderKey, - authenticatorParameters.getUserIdentity().getProviderLogin(), - authenticatorParameters.getUserIdentity().getProviderId())) - .build(); - } - - private static UserDto[] toArray(Optional<UserDto> userDto) { - return userDto.map(u -> new UserDto[] {u}).orElse(new UserDto[] {}); - } - - private static AuthenticationException generateExistingEmailError(UserRegistration authenticatorParameters, String email) { - return AuthenticationException.newBuilder() - .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)) - .build(); - } - - private static String getProviderIdOrProviderLogin(UserIdentity userIdentity) { - String providerId = userIdentity.getProviderId(); - return providerId == null ? userIdentity.getProviderLogin() : providerId; - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistration.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistration.java deleted file mode 100644 index fc6dc4a8d4d..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserRegistration.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Set; -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.server.authentication.event.AuthenticationEvent; - -import static java.util.Objects.requireNonNull; - -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; - } - - public UserIdentity getUserIdentity() { - return userIdentity; - } - - public IdentityProvider getProvider() { - return provider; - } - - public AuthenticationEvent.Source getSource() { - return source; - } - - public ExistingEmailStrategy getExistingEmailStrategy() { - return existingEmailStrategy; - } - - @CheckForNull - public Set<String> getOrganizationAlmIds() { - return organizationAlmIds; - } - - static UserRegistration.Builder builder() { - return new Builder(); - } - - public static class Builder { - private UserIdentity userIdentity; - private IdentityProvider provider; - private AuthenticationEvent.Source source; - private ExistingEmailStrategy existingEmailStrategy; - private Set<String> organizationAlmIds; - - public Builder setUserIdentity(UserIdentity userIdentity) { - this.userIdentity = userIdentity; - return this; - } - - public Builder setProvider(IdentityProvider provider) { - this.provider = provider; - return this; - } - - public Builder setSource(AuthenticationEvent.Source source) { - this.source = source; - 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. - */ - public Builder setOrganizationAlmIds(@Nullable Set<String> organizationAlmIds) { - this.organizationAlmIds = organizationAlmIds; - return this; - } - - public UserRegistration build() { - 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); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java deleted file mode 100644 index 4f28a4cad4e..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.collect.ImmutableSet; -import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.config.Configuration; -import org.sonar.api.server.ServerSide; -import org.sonar.api.web.ServletFilter.UrlPattern; -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.user.ThreadLocalUserSession; -import org.sonar.server.user.UserSession; - -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; -import static org.apache.commons.lang.StringUtils.defaultString; -import static org.sonar.api.CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY; -import static org.sonar.api.web.ServletFilter.UrlPattern.Builder.staticResourcePatterns; -import static org.sonar.server.authentication.AuthenticationError.handleAuthenticationError; - -@ServerSide -public class UserSessionInitializer { - - /** - * Key of attribute to be used for displaying user login - * in logs/access.log. The pattern to be configured - * in property sonar.web.accessLogs.pattern is "%reqAttribute{LOGIN}" - */ - private static final String ACCESS_LOG_LOGIN = "LOGIN"; - - // SONAR-6546 these urls should be get from WebService - private static final Set<String> SKIPPED_URLS = ImmutableSet.of( - "/batch/index", "/batch/file", - "/maintenance/*", "/setup/*", - "/sessions/*", "/oauth2/callback/*", - "/api/system/db_migration_status", "/api/system/status", "/api/system/migrate_db", - "/api/server/version", - "/api/users/identity_providers", "/api/l10n/index", - "/api/authentication/login", "/api/authentication/logout", "/api/authentication/validate"); - - private static final Set<String> URL_USING_PASSCODE = ImmutableSet.of( - "/api/ce/info", "/api/ce/pause", "/api/ce/resume", "/api/system/health", "/api/system/analytics", "/api/system/migrate_es"); - - private static final UrlPattern URL_PATTERN = UrlPattern.builder() - .includes("/*") - .excludes(staticResourcePatterns()) - .excludes(SKIPPED_URLS) - .build(); - - private static final UrlPattern PASSCODE_URLS = UrlPattern.builder() - .includes(URL_USING_PASSCODE) - .build(); - - private final Configuration config; - private final ThreadLocalUserSession threadLocalSession; - private final AuthenticationEvent authenticationEvent; - private final RequestAuthenticator requestAuthenticator; - - public UserSessionInitializer(Configuration config, ThreadLocalUserSession threadLocalSession, AuthenticationEvent authenticationEvent, - RequestAuthenticator requestAuthenticator) { - this.config = config; - this.threadLocalSession = threadLocalSession; - this.authenticationEvent = authenticationEvent; - this.requestAuthenticator = requestAuthenticator; - } - - public boolean initUserSession(HttpServletRequest request, HttpServletResponse response) { - String path = request.getRequestURI().replaceFirst(request.getContextPath(), ""); - try { - // Do not set user session when url is excluded - if (URL_PATTERN.matches(path)) { - loadUserSession(request, response, PASSCODE_URLS.matches(path)); - } - return true; - } catch (AuthenticationException e) { - authenticationEvent.loginFailure(request, e); - if (isWsUrl(path)) { - response.setStatus(HTTP_UNAUTHORIZED); - return false; - } - if (isNotLocalOrJwt(e.getSource())) { - // redirect to Unauthorized error page - handleAuthenticationError(e, request, response); - return false; - } - // Web pages should redirect to the index.html file - return true; - } - } - - private static boolean isNotLocalOrJwt(Source source) { - AuthenticationEvent.Provider provider = source.getProvider(); - return provider != AuthenticationEvent.Provider.LOCAL && provider != AuthenticationEvent.Provider.JWT; - } - - private void loadUserSession(HttpServletRequest request, HttpServletResponse response, boolean urlSupportsSystemPasscode) { - UserSession session = requestAuthenticator.authenticate(request, response); - if (!session.isLoggedIn() && !urlSupportsSystemPasscode && config.getBoolean(CORE_FORCE_AUTHENTICATION_PROPERTY).orElse(false)) { - // authentication is required - throw AuthenticationException.newBuilder() - .setSource(Source.local(AuthenticationEvent.Method.BASIC)) - .setMessage("User must be authenticated") - .build(); - } - threadLocalSession.set(session); - request.setAttribute(ACCESS_LOG_LOGIN, defaultString(session.getLogin(), "-")); - } - - public void removeUserSession() { - threadLocalSession.unload(); - } - - private static boolean isWsUrl(String path) { - return path.startsWith("/batch/") || path.startsWith("/api/"); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java deleted file mode 100644 index 0e61085c6cc..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.event; - -import java.io.Serializable; -import java.util.Objects; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import org.sonar.api.server.authentication.BaseIdentityProvider; -import org.sonar.api.server.authentication.IdentityProvider; -import org.sonar.api.server.authentication.OAuth2IdentityProvider; - -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Objects.requireNonNull; - -public interface AuthenticationEvent { - - void loginSuccess(HttpServletRequest request, @Nullable String login, Source source); - - void loginFailure(HttpServletRequest request, AuthenticationException e); - - void logoutSuccess(HttpServletRequest request, @Nullable String login); - - void logoutFailure(HttpServletRequest request, String errorMessage); - - enum Method { - /** - * HTTP basic authentication with a login and password. - */ - BASIC, - /** - * HTTP basic authentication with a security token. - */ - BASIC_TOKEN, - /** - * SQ login form authentication with a login and password. - */ - FORM, - /** - * SSO authentication (ie. with HTTP headers) - */ - SSO, - /** - * OAUTH2 authentication. - */ - OAUTH2, - /** - * JWT authentication (ie. with a session token). - */ - JWT, - /** - * External authentication (ie. fully implemented out of SQ's core code, see {@link BaseIdentityProvider}). - */ - EXTERNAL - } - - enum Provider { - /** - * User authentication made against data in SQ's User table. - */ - LOCAL, - /** - * User authentication made by SSO provider. - */ - SSO, - /** - * User authentication made by Realm based provider (eg. LDAP). - */ - REALM, - /** - * User authentication made by JWT token information. - */ - JWT, - /** - * User authentication made by external provider (see {@link BaseIdentityProvider}). - */ - EXTERNAL - } - - final class Source implements Serializable { - private static final String LOCAL_PROVIDER_NAME = "local"; - private static final Source SSO_INSTANCE = new Source(Method.SSO, Provider.SSO, "sso"); - private static final Source JWT_INSTANCE = new Source(Method.JWT, Provider.JWT, "jwt"); - - private final Method method; - private final Provider provider; - private final String providerName; - - private Source(Method method, Provider provider, String providerName) { - this.method = requireNonNull(method, "method can't be null"); - this.provider = requireNonNull(provider, "provider can't be null"); - this.providerName = requireNonNull(providerName, "provider name can't be null"); - checkArgument(!providerName.isEmpty(), "provider name can't be empty"); - } - - public static Source local(Method method) { - return new Source(method, Provider.LOCAL, LOCAL_PROVIDER_NAME); - } - - public static Source oauth2(OAuth2IdentityProvider identityProvider) { - return new Source( - Method.OAUTH2, Provider.EXTERNAL, - requireNonNull(identityProvider, "identityProvider can't be null").getName()); - } - - public static Source realm(Method method, String providerName) { - return new Source(method, Provider.REALM, providerName); - } - - public static Source sso() { - return SSO_INSTANCE; - } - - public static Source jwt() { - return JWT_INSTANCE; - } - - public static Source external(IdentityProvider identityProvider) { - return new Source( - Method.EXTERNAL, Provider.EXTERNAL, - requireNonNull(identityProvider, "identityProvider can't be null").getName()); - } - - public Method getMethod() { - return method; - } - - public Provider getProvider() { - return provider; - } - - public String getProviderName() { - return providerName; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Source source = (Source) o; - return method == source.method && - provider == source.provider && - providerName.equals(source.providerName); - } - - @Override - public int hashCode() { - return Objects.hash(method, provider, providerName); - } - - @Override - public String toString() { - return "Source{" + - "method=" + method + - ", provider=" + provider + - ", providerName='" + providerName + '\'' + - '}'; - } - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEventImpl.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEventImpl.java deleted file mode 100644 index 42a5c90acf0..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationEventImpl.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.event; - -import com.google.common.base.Joiner; -import java.util.Collections; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.core.util.stream.MoreCollectors; - -import static java.util.Objects.requireNonNull; - -public class AuthenticationEventImpl implements AuthenticationEvent { - private static final Logger LOGGER = Loggers.get("auth.event"); - private static final int FLOOD_THRESHOLD = 128; - - @Override - public void loginSuccess(HttpServletRequest request, @Nullable String login, Source source) { - checkRequest(request); - requireNonNull(source, "source can't be null"); - if (!LOGGER.isDebugEnabled()) { - return; - } - LOGGER.debug("login success [method|{}][provider|{}|{}][IP|{}|{}][login|{}]", - source.getMethod(), source.getProvider(), source.getProviderName(), - request.getRemoteAddr(), getAllIps(request), - preventLogFlood(emptyIfNull(login))); - } - - private static String getAllIps(HttpServletRequest request) { - return Collections.list(request.getHeaders("X-Forwarded-For")).stream().collect(MoreCollectors.join(Joiner.on(","))); - } - - @Override - public void loginFailure(HttpServletRequest request, AuthenticationException e) { - checkRequest(request); - requireNonNull(e, "AuthenticationException can't be null"); - if (!LOGGER.isDebugEnabled()) { - return; - } - Source source = e.getSource(); - LOGGER.debug("login failure [cause|{}][method|{}][provider|{}|{}][IP|{}|{}][login|{}]", - emptyIfNull(e.getMessage()), - source.getMethod(), source.getProvider(), source.getProviderName(), - request.getRemoteAddr(), getAllIps(request), - preventLogFlood(emptyIfNull(e.getLogin()))); - } - - @Override - public void logoutSuccess(HttpServletRequest request, @Nullable String login) { - checkRequest(request); - if (!LOGGER.isDebugEnabled()) { - return; - } - LOGGER.debug("logout success [IP|{}|{}][login|{}]", - request.getRemoteAddr(), getAllIps(request), - preventLogFlood(emptyIfNull(login))); - } - - @Override - public void logoutFailure(HttpServletRequest request, String errorMessage) { - checkRequest(request); - requireNonNull(errorMessage, "error message can't be null"); - if (!LOGGER.isDebugEnabled()) { - return; - } - LOGGER.debug("logout failure [error|{}][IP|{}|{}]", - emptyIfNull(errorMessage), - request.getRemoteAddr(), getAllIps(request)); - } - - private static void checkRequest(HttpServletRequest request) { - requireNonNull(request, "request can't be null"); - } - - private static String emptyIfNull(@Nullable String login) { - return login == null ? "" : login; - } - - private static String preventLogFlood(String str) { - if (str.length() > FLOOD_THRESHOLD) { - return str.substring(0, FLOOD_THRESHOLD) + "...(" + str.length() + ")"; - } - return str; - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationException.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationException.java deleted file mode 100644 index a4b7196232a..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/AuthenticationException.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.event; - -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - -import static java.util.Objects.requireNonNull; - -/** - * Exception thrown in case of authentication failure. - * <p> - * This exception contains the source of authentication and, if present, the login on which the login attempt occurred. - * </p> - * <p> - * Given that {@link #source} and {@link #login} will be logged to file, be very careful <strong>not to set the login - * when the login is a security token</strong>. - * </p> - */ -public class AuthenticationException extends RuntimeException { - private final AuthenticationEvent.Source source; - @CheckForNull - private final String login; - private final String publicMessage; - - private AuthenticationException(Builder builder) { - super(builder.message); - this.source = requireNonNull(builder.source, "source can't be null"); - this.login = builder.login; - this.publicMessage = builder.publicMessage; - } - - public AuthenticationEvent.Source getSource() { - return source; - } - - @CheckForNull - public String getLogin() { - return login; - } - - @CheckForNull - public String getPublicMessage() { - return publicMessage; - } - - public static Builder newBuilder() { - return new Builder(); - } - - public static class Builder { - @CheckForNull - private AuthenticationEvent.Source source; - @CheckForNull - private String login; - @CheckForNull - private String message; - @CheckForNull - private String publicMessage; - - private Builder() { - // use static factory method - } - - public Builder setSource(AuthenticationEvent.Source source) { - this.source = source; - return this; - } - - public Builder setLogin(@Nullable String login) { - this.login = login; - return this; - } - - public Builder setMessage(String message) { - this.message = message; - return this; - } - - public Builder setPublicMessage(String publicMessage) { - this.publicMessage = publicMessage; - return this; - } - - public AuthenticationException build() { - return new AuthenticationException(this); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/event/package-info.java deleted file mode 100644 index 329add30331..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/event/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.authentication.event; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/EmailAlreadyExistsRedirectionException.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/EmailAlreadyExistsRedirectionException.java deleted file mode 100644 index c2d103709b4..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/EmailAlreadyExistsRedirectionException.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/RedirectionException.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/RedirectionException.java deleted file mode 100644 index b566ef376c4..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/RedirectionException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -public abstract class RedirectionException extends RuntimeException { - - public abstract String getPath(String contextPath); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/package-info.java deleted file mode 100644 index af6555c0df2..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/exception/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.authentication.exception; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/package-info.java deleted file mode 100644 index 6ce3daa6d3c..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.authentication; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationEnforcer.java b/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationEnforcer.java deleted file mode 100644 index 6c8aa140701..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/DefaultOrganizationEnforcer.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import org.picocontainer.Startable; - -public class DefaultOrganizationEnforcer implements Startable { - private final DefaultOrganizationProvider defaultOrganizationProvider; - - public DefaultOrganizationEnforcer(DefaultOrganizationProvider defaultOrganizationProvider) { - this.defaultOrganizationProvider = defaultOrganizationProvider; - } - - @Override - public void start() { - // get will fail with IllegalStateException if no default organization can be found - defaultOrganizationProvider.get(); - } - - @Override - public void stop() { - // nothing to do - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/MemberUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/organization/MemberUpdater.java deleted file mode 100644 index 0f6c126a6a6..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/MemberUpdater.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.alm.ALM; -import org.sonar.db.alm.OrganizationAlmBindingDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.organization.OrganizationMemberDto; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserGroupDto; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Sets.difference; -import static com.google.common.collect.Sets.union; -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toSet; -import static org.sonar.api.CoreProperties.DEFAULT_ISSUE_ASSIGNEE; -import static org.sonar.core.util.stream.MoreCollectors.toList; -import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; -import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; - -public class MemberUpdater { - - private final DbClient dbClient; - private final DefaultGroupFinder defaultGroupFinder; - private final UserIndexer userIndexer; - - public MemberUpdater(DbClient dbClient, DefaultGroupFinder defaultGroupFinder, UserIndexer userIndexer) { - this.dbClient = dbClient; - this.defaultGroupFinder = defaultGroupFinder; - this.userIndexer = userIndexer; - } - - public void addMember(DbSession dbSession, OrganizationDto organization, UserDto user) { - addMembers(dbSession, organization, singletonList(user)); - } - - public void addMembers(DbSession dbSession, OrganizationDto organization, List<UserDto> users) { - Set<Integer> currentMemberIds = new HashSet<>(dbClient.organizationMemberDao().selectUserIdsByOrganizationUuid(dbSession, organization.getUuid())); - List<UserDto> usersToAdd = users.stream() - .filter(UserDto::isActive) - .filter(u -> !currentMemberIds.contains(u.getId())) - .collect(toList()); - if (usersToAdd.isEmpty()) { - return; - } - usersToAdd.forEach(u -> addMemberInDb(dbSession, organization, u)); - userIndexer.commitAndIndex(dbSession, usersToAdd); - } - - private void addMemberInDb(DbSession dbSession, OrganizationDto organization, UserDto user) { - dbClient.organizationMemberDao().insert(dbSession, new OrganizationMemberDto() - .setOrganizationUuid(organization.getUuid()) - .setUserId(user.getId())); - dbClient.userGroupDao().insert(dbSession, - new UserGroupDto().setGroupId(defaultGroupFinder.findDefaultGroup(dbSession, organization.getUuid()).getId()).setUserId(user.getId())); - } - - public void removeMember(DbSession dbSession, OrganizationDto organization, UserDto user) { - removeMembers(dbSession, organization, singletonList(user)); - } - - public void removeMembers(DbSession dbSession, OrganizationDto organization, List<UserDto> users) { - Set<Integer> currentMemberIds = new HashSet<>(dbClient.organizationMemberDao().selectUserIdsByOrganizationUuid(dbSession, organization.getUuid())); - List<UserDto> usersToRemove = users.stream() - .filter(UserDto::isActive) - .filter(u -> currentMemberIds.contains(u.getId())) - .collect(toList()); - if (usersToRemove.isEmpty()) { - return; - } - - Set<Integer> userIdsToRemove = usersToRemove.stream().map(UserDto::getId).collect(toSet()); - Set<Integer> adminIds = new HashSet<>(dbClient.authorizationDao().selectUserIdsWithGlobalPermission(dbSession, organization.getUuid(), ADMINISTER.getKey())); - checkArgument(!difference(adminIds, userIdsToRemove).isEmpty(), "The last administrator member cannot be removed"); - - usersToRemove.forEach(u -> removeMemberInDb(dbSession, organization, u)); - userIndexer.commitAndIndex(dbSession, usersToRemove); - } - - /** - * Synchronize organization membership of a user from a list of ALM organization specific ids - * Please note that no commit will not be executed. - */ - public void synchronizeUserOrganizationMembership(DbSession dbSession, UserDto user, ALM alm, Set<String> organizationAlmIds) { - Set<String> userOrganizationUuids = dbClient.organizationMemberDao().selectOrganizationUuidsByUser(dbSession, user.getId()); - Set<String> userOrganizationUuidsWithMembersSyncEnabled = dbClient.organizationAlmBindingDao().selectByOrganizationUuids(dbSession, userOrganizationUuids).stream() - .filter(OrganizationAlmBindingDto::isMembersSyncEnable) - .map(OrganizationAlmBindingDto::getOrganizationUuid) - .collect(toSet()); - Set<String> almOrganizationUuidsWithMembersSyncEnabled = dbClient.organizationAlmBindingDao().selectByOrganizationAlmIds(dbSession, alm, organizationAlmIds).stream() - .filter(OrganizationAlmBindingDto::isMembersSyncEnable) - .map(OrganizationAlmBindingDto::getOrganizationUuid) - .collect(toSet()); - - Set<String> organizationUuidsToBeAdded = difference(almOrganizationUuidsWithMembersSyncEnabled, userOrganizationUuidsWithMembersSyncEnabled); - Set<String> organizationUuidsToBeRemoved = difference(userOrganizationUuidsWithMembersSyncEnabled, almOrganizationUuidsWithMembersSyncEnabled); - Map<String, OrganizationDto> allOrganizationsByUuid = dbClient.organizationDao().selectByUuids(dbSession, union(organizationUuidsToBeAdded, organizationUuidsToBeRemoved)) - .stream() - .collect(uniqueIndex(OrganizationDto::getUuid)); - - allOrganizationsByUuid.entrySet().stream() - .filter(entry -> organizationUuidsToBeAdded.contains(entry.getKey())) - .forEach(entry -> addMemberInDb(dbSession, entry.getValue(), user)); - allOrganizationsByUuid.entrySet().stream() - .filter(entry -> organizationUuidsToBeRemoved.contains(entry.getKey())) - .forEach(entry -> removeMemberInDb(dbSession, entry.getValue(), user)); - } - - private void removeMemberInDb(DbSession dbSession, OrganizationDto organization, UserDto user) { - int userId = user.getId(); - String organizationUuid = organization.getUuid(); - dbClient.userPermissionDao().deleteOrganizationMemberPermissions(dbSession, organizationUuid, userId); - dbClient.permissionTemplateDao().deleteUserPermissionsByOrganization(dbSession, organizationUuid, userId); - dbClient.qProfileEditUsersDao().deleteByOrganizationAndUser(dbSession, organization, user); - dbClient.userGroupDao().deleteByOrganizationAndUser(dbSession, organizationUuid, userId); - dbClient.propertiesDao().deleteByOrganizationAndUser(dbSession, organizationUuid, userId); - dbClient.propertiesDao().deleteByOrganizationAndMatchingLogin(dbSession, organizationUuid, user.getLogin(), singletonList(DEFAULT_ISSUE_ASSIGNEE)); - - dbClient.organizationMemberDao().delete(dbSession, organizationUuid, userId); - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/NoopDefaultOrganizationCache.java b/server/sonar-server/src/main/java/org/sonar/server/organization/NoopDefaultOrganizationCache.java deleted file mode 100644 index cfb1cbad52b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/NoopDefaultOrganizationCache.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -/** - * This implementation of {@link DefaultOrganizationCache} has no effect. It is used when SQ is running in safe mode - * (ie. when we are expecting a DB upgrade and we can't assume the tables storing default organization information are - * available). - */ -public class NoopDefaultOrganizationCache implements DefaultOrganizationCache { - @Override - public void load() { - // do nothing - } - - @Override - public void unload() { - // do nothing - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganisationSupport.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganisationSupport.java deleted file mode 100644 index 0198cae2b4e..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganisationSupport.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import java.util.List; -import org.sonar.api.rule.RuleStatus; -import org.sonar.api.server.ServerSide; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.permission.GroupPermissionDto; -import org.sonar.db.permission.template.PermissionTemplateGroupDto; -import org.sonar.db.rule.RuleDefinitionDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserGroupDto; -import org.sonar.server.rule.index.RuleIndexer; -import org.sonar.server.usergroups.DefaultGroupCreator; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static org.sonar.core.util.stream.MoreCollectors.toList; - -@ServerSide -public class OrganisationSupport { - private final DbClient dbClient; - private final DefaultOrganizationProvider defaultOrganizationProvider; - private final OrganizationFlags organizationFlags; - private final DefaultGroupCreator defaultGroupCreator; - private final DefaultGroupFinder defaultGroupFinder; - private final RuleIndexer ruleIndexer; - - public OrganisationSupport(DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider, - OrganizationFlags organizationFlags, DefaultGroupCreator defaultGroupCreator, DefaultGroupFinder defaultGroupFinder, - RuleIndexer ruleIndexer) { - this.dbClient = dbClient; - this.defaultOrganizationProvider = defaultOrganizationProvider; - this.organizationFlags = organizationFlags; - this.defaultGroupCreator = defaultGroupCreator; - this.defaultGroupFinder = defaultGroupFinder; - this.ruleIndexer = ruleIndexer; - } - - public void enable(String login) { - String defaultOrganizationUuid = defaultOrganizationProvider.get().getUuid(); - try (DbSession dbSession = dbClient.openSession(false)) { - if (!organizationFlags.isEnabled(dbSession)) { - flagAdminUserAsRoot(dbSession, login); - createDefaultMembersGroup(dbSession, defaultOrganizationUuid); - List<Integer> disabledTemplateAndCustomRuleIds = disableTemplateRulesAndCustomRules(dbSession); - enableFeature(dbSession); - ruleIndexer.commitAndIndex(dbSession, disabledTemplateAndCustomRuleIds); - } - } - } - - private void flagAdminUserAsRoot(DbSession dbSession, String login) { - dbClient.userDao().setRoot(dbSession, login, true); - } - - private void createDefaultMembersGroup(DbSession dbSession, String defaultOrganizationUuid) { - GroupDto sonarUsersGroupId = defaultGroupFinder.findDefaultGroup(dbSession, defaultOrganizationUuid); - GroupDto members = defaultGroupCreator.create(dbSession, defaultOrganizationUuid); - copySonarUsersGroupPermissionsToMembersGroup(dbSession, defaultOrganizationUuid, sonarUsersGroupId, members); - copySonarUsersGroupPermissionTemplatesToMembersGroup(dbSession, sonarUsersGroupId, members); - associateMembersOfDefaultOrganizationToGroup(dbSession, defaultOrganizationUuid, members); - } - - private void associateMembersOfDefaultOrganizationToGroup(DbSession dbSession, String defaultOrganizationUuid, GroupDto membersGroup) { - List<Integer> organizationMembers = dbClient.organizationMemberDao().selectUserIdsByOrganizationUuid(dbSession, defaultOrganizationUuid); - organizationMembers.forEach(member -> dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupId(membersGroup.getId()).setUserId(member))); - } - - private void copySonarUsersGroupPermissionsToMembersGroup(DbSession dbSession, String defaultOrganizationUuid, GroupDto sonarUsersGroup, GroupDto membersGroup) { - dbClient.groupPermissionDao().selectAllPermissionsByGroupId(dbSession, defaultOrganizationUuid, sonarUsersGroup.getId(), - context -> { - GroupPermissionDto groupPermissionDto = (GroupPermissionDto) context.getResultObject(); - dbClient.groupPermissionDao().insert(dbSession, - new GroupPermissionDto().setOrganizationUuid(defaultOrganizationUuid).setGroupId(membersGroup.getId()) - .setRole(groupPermissionDto.getRole()) - .setResourceId(groupPermissionDto.getResourceId())); - }); - } - - private void copySonarUsersGroupPermissionTemplatesToMembersGroup(DbSession dbSession, GroupDto sonarUsersGroup, GroupDto membersGroup) { - List<PermissionTemplateGroupDto> sonarUsersPermissionTemplates = dbClient.permissionTemplateDao().selectAllGroupPermissionTemplatesByGroupId(dbSession, - sonarUsersGroup.getId()); - sonarUsersPermissionTemplates.forEach(permissionTemplateGroup -> dbClient.permissionTemplateDao().insertGroupPermission(dbSession, - permissionTemplateGroup.getTemplateId(), membersGroup.getId(), permissionTemplateGroup.getPermission())); - } - - private List<Integer> disableTemplateRulesAndCustomRules(DbSession dbSession) { - List<RuleDefinitionDto> rules = dbClient.ruleDao().selectAllDefinitions(dbSession).stream() - .filter(r -> r.isTemplate() || r.isCustomRule()) - .collect(toList()); - rules.forEach(r -> { - r.setStatus(RuleStatus.REMOVED); - dbClient.ruleDao().update(dbSession, r); - }); - return rules.stream().map(RuleDefinitionDto::getId).collect(toList()); - } - - private void enableFeature(DbSession dbSession) { - organizationFlags.enable(dbSession); - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationAlmBinding.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationAlmBinding.java deleted file mode 100644 index 80584ff7f47..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationAlmBinding.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import org.sonar.api.server.ServerSide; -import org.sonar.db.DbSession; -import org.sonar.db.organization.OrganizationDto; - -@ServerSide -public interface OrganizationAlmBinding { - - void bindOrganization(DbSession dbSession, OrganizationDto organization, String installationId, boolean isNewOrganization); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdater.java deleted file mode 100644 index de4f15b2ee7..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdater.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import java.util.function.Consumer; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.sonar.api.web.UserRole; -import org.sonar.db.DbSession; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.usergroups.DefaultGroupCreatorImpl; - -import static java.util.Objects.requireNonNull; - -public interface OrganizationUpdater { - String OWNERS_GROUP_NAME = "Owners"; - String OWNERS_GROUP_DESCRIPTION = "Owners of organization"; - String PERM_TEMPLATE_NAME = "Default template"; - String PERM_TEMPLATE_DESCRIPTION_PATTERN = "Default permission template of organization %s"; - - /** - * Create a new organization with the specified properties and of which the specified user will assign - * Administer Organization permission. - * <p> - * This method does several operations at once: - * <ol> - * <li>create an ungarded organization with the specified details</li> - * <li>create a group called {@link #OWNERS_GROUP_NAME Owners} with all organization wide permissions</li> - * <li>create a group called {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} with browse permissions</li> - * <li>make the specified user a member of these groups</li> - * <li>create a default template for the organization - * <ul> - * <li>name is {@link #PERM_TEMPLATE_NAME Default template}</li> - * <li>description follows pattern {@link #PERM_TEMPLATE_DESCRIPTION_PATTERN} based on the organization name</li> - * </ul> - * </li> - * <li>this permission template defines the specified permissions (which effectively makes projects public): - * <ul> - * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#ADMIN ADMIN}</li> - * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#ISSUE_ADMIN ISSUE_ADMIN}</li> - * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#SECURITYHOTSPOT_ADMIN SECURITYHOTSPOT_ADMIN}</li> - * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#SCAN SCAN}</li> - * <li>group {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} : {@link UserRole#USER USER}</li> - * <li>group {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} : {@link UserRole#CODEVIEWER CODEVIEWER}</li> - * </ul> - * </li> - * </ol> - * </p> - * - * @return the created organization - * - * @throws KeyConflictException if an organization with the specified key already exists - * @throws IllegalArgumentException if any field of {@code newOrganization} is invalid according to {@link OrganizationValidation} - */ - OrganizationDto create(DbSession dbSession, UserDto userCreator, NewOrganization newOrganization, Consumer<OrganizationDto> beforeCommit) throws KeyConflictException; - - /** - * Update the personal organization key of a user. - * No update will be performed if generated key match the same key as existing one. - * - * @throws IllegalStateException if user has no no personal organization - * @throws IllegalStateException if personal organization uuid does not exist - * @throws IllegalStateException if an organization with the key generated from the login already exists - */ - void updateOrganizationKey(DbSession dbSession, OrganizationDto organization, String newKey); - - final class KeyConflictException extends Exception { - KeyConflictException(String message) { - super(message); - } - } - - final class NewOrganization { - private final String key; - private final String name; - @CheckForNull - private final String description; - @CheckForNull - private final String url; - @CheckForNull - private final String avatar; - - private NewOrganization(Builder builder) { - this.key = builder.key; - this.name = builder.name; - this.description = builder.description; - this.url = builder.url; - this.avatar = builder.avatarUrl; - } - - public String getKey() { - return key; - } - - public String getName() { - return name; - } - - @CheckForNull - public String getDescription() { - return description; - } - - @CheckForNull - public String getUrl() { - return url; - } - - @CheckForNull - public String getAvatar() { - return avatar; - } - - public static NewOrganization.Builder newOrganizationBuilder() { - return new Builder(); - } - - public static final class Builder { - private String key; - private String name; - private String description; - private String url; - private String avatarUrl; - - private Builder() { - // use factory method - } - - public Builder setKey(String key) { - this.key = requireNonNull(key, "key can't be null"); - return this; - } - - public Builder setName(String name) { - this.name = requireNonNull(name, "name can't be null"); - return this; - } - - public Builder setDescription(@Nullable String description) { - this.description = description; - return this; - } - - public Builder setUrl(@Nullable String url) { - this.url = url; - return this; - } - - public Builder setAvatarUrl(@Nullable String avatarUrl) { - this.avatarUrl = avatarUrl; - return this; - } - - public NewOrganization build() { - requireNonNull(key, "key can't be null"); - requireNonNull(name, "name can't be null"); - return new NewOrganization(this); - } - } - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdaterImpl.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdaterImpl.java deleted file mode 100644 index 38f4a3c1a17..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdaterImpl.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import javax.annotation.Nullable; -import org.sonar.api.utils.System2; -import org.sonar.core.util.UuidFactory; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.organization.DefaultTemplates; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.organization.OrganizationMemberDto; -import org.sonar.db.permission.GroupPermissionDto; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.db.permission.template.PermissionTemplateDto; -import org.sonar.db.qualitygate.QualityGateDto; -import org.sonar.db.qualityprofile.DefaultQProfileDto; -import org.sonar.db.qualityprofile.OrgQProfileDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserGroupDto; -import org.sonar.server.permission.PermissionService; -import org.sonar.server.qualityprofile.BuiltInQProfile; -import org.sonar.server.qualityprofile.BuiltInQProfileRepository; -import org.sonar.server.qualityprofile.QProfileName; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.usergroups.DefaultGroupCreator; - -import static com.google.common.base.Preconditions.checkState; -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; -import static org.sonar.api.web.UserRole.ADMIN; -import static org.sonar.api.web.UserRole.CODEVIEWER; -import static org.sonar.api.web.UserRole.ISSUE_ADMIN; -import static org.sonar.api.web.UserRole.SECURITYHOTSPOT_ADMIN; -import static org.sonar.api.web.UserRole.USER; -import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; -import static org.sonar.db.organization.OrganizationDto.Subscription.FREE; -import static org.sonar.db.permission.OrganizationPermission.SCAN; - -public class OrganizationUpdaterImpl implements OrganizationUpdater { - - private final DbClient dbClient; - private final System2 system2; - private final UuidFactory uuidFactory; - private final OrganizationValidation organizationValidation; - private final BuiltInQProfileRepository builtInQProfileRepository; - private final DefaultGroupCreator defaultGroupCreator; - private final UserIndexer userIndexer; - private final PermissionService permissionService; - - public OrganizationUpdaterImpl(DbClient dbClient, System2 system2, UuidFactory uuidFactory, - OrganizationValidation organizationValidation, UserIndexer userIndexer, - BuiltInQProfileRepository builtInQProfileRepository, DefaultGroupCreator defaultGroupCreator, PermissionService permissionService) { - this.dbClient = dbClient; - this.system2 = system2; - this.uuidFactory = uuidFactory; - this.organizationValidation = organizationValidation; - this.userIndexer = userIndexer; - this.builtInQProfileRepository = builtInQProfileRepository; - this.defaultGroupCreator = defaultGroupCreator; - this.permissionService = permissionService; - } - - @Override - public OrganizationDto create(DbSession dbSession, UserDto userCreator, NewOrganization newOrganization, Consumer<OrganizationDto> beforeCommit) throws KeyConflictException { - validate(newOrganization); - String key = newOrganization.getKey(); - if (organizationKeyIsUsed(dbSession, key)) { - throw new KeyConflictException(format("Organization key '%s' is already used", key)); - } - - QualityGateDto builtInQualityGate = dbClient.qualityGateDao().selectBuiltIn(dbSession); - OrganizationDto organization = insertOrganization(dbSession, newOrganization, builtInQualityGate); - beforeCommit.accept(organization); - insertOrganizationMember(dbSession, organization, userCreator.getId()); - dbClient.qualityGateDao().associate(dbSession, uuidFactory.create(), organization, builtInQualityGate); - GroupDto ownerGroup = insertOwnersGroup(dbSession, organization); - GroupDto defaultGroup = defaultGroupCreator.create(dbSession, organization.getUuid()); - insertDefaultTemplateOnGroups(dbSession, organization, ownerGroup, defaultGroup); - addCurrentUserToGroup(dbSession, ownerGroup, userCreator.getId()); - addCurrentUserToGroup(dbSession, defaultGroup, userCreator.getId()); - try (DbSession batchDbSession = dbClient.openSession(true)) { - insertQualityProfiles(dbSession, batchDbSession, organization); - batchDbSession.commit(); - - // Elasticsearch is updated when DB session is committed - userIndexer.commitAndIndex(dbSession, userCreator); - - return organization; - } - } - - @Override - public void updateOrganizationKey(DbSession dbSession, OrganizationDto organization, String newKey) { - String sanitizedKey = organizationValidation.generateKeyFrom(newKey); - if (organization.getKey().equals(sanitizedKey)) { - return; - } - checkKey(dbSession, sanitizedKey); - dbClient.organizationDao().update(dbSession, organization.setKey(sanitizedKey)); - } - - private void checkKey(DbSession dbSession, String key) { - checkState(!organizationKeyIsUsed(dbSession, key), - "Can't create organization with key '%s' because an organization with this key already exists", key); - } - - private void validate(NewOrganization newOrganization) { - requireNonNull(newOrganization, "newOrganization can't be null"); - organizationValidation.checkName(newOrganization.getName()); - organizationValidation.checkKey(newOrganization.getKey()); - organizationValidation.checkDescription(newOrganization.getDescription()); - organizationValidation.checkUrl(newOrganization.getUrl()); - organizationValidation.checkAvatar(newOrganization.getAvatar()); - } - - private OrganizationDto insertOrganization(DbSession dbSession, NewOrganization newOrganization, QualityGateDto builtInQualityGate, Consumer<OrganizationDto>... extendCreation) { - OrganizationDto res = new OrganizationDto() - .setUuid(uuidFactory.create()) - .setName(newOrganization.getName()) - .setKey(newOrganization.getKey()) - .setDescription(newOrganization.getDescription()) - .setUrl(newOrganization.getUrl()) - .setDefaultQualityGateUuid(builtInQualityGate.getUuid()) - .setAvatarUrl(newOrganization.getAvatar()) - .setSubscription(FREE); - Arrays.stream(extendCreation).forEach(c -> c.accept(res)); - dbClient.organizationDao().insert(dbSession, res, false); - return res; - } - - private boolean organizationKeyIsUsed(DbSession dbSession, String key) { - return dbClient.organizationDao().selectByKey(dbSession, key).isPresent(); - } - - private void insertDefaultTemplateOnGroups(DbSession dbSession, OrganizationDto organizationDto, GroupDto ownerGroup, GroupDto defaultGroup) { - Date now = new Date(system2.now()); - PermissionTemplateDto permissionTemplateDto = dbClient.permissionTemplateDao().insert( - dbSession, - new PermissionTemplateDto() - .setOrganizationUuid(organizationDto.getUuid()) - .setUuid(uuidFactory.create()) - .setName(PERM_TEMPLATE_NAME) - .setDescription(format(PERM_TEMPLATE_DESCRIPTION_PATTERN, organizationDto.getName())) - .setCreatedAt(now) - .setUpdatedAt(now)); - - insertGroupPermission(dbSession, permissionTemplateDto, ADMIN, ownerGroup); - insertGroupPermission(dbSession, permissionTemplateDto, SCAN.getKey(), ownerGroup); - insertGroupPermission(dbSession, permissionTemplateDto, USER, defaultGroup); - insertGroupPermission(dbSession, permissionTemplateDto, CODEVIEWER, defaultGroup); - insertGroupPermission(dbSession, permissionTemplateDto, ISSUE_ADMIN, defaultGroup); - insertGroupPermission(dbSession, permissionTemplateDto, SECURITYHOTSPOT_ADMIN, defaultGroup); - - dbClient.organizationDao().setDefaultTemplates( - dbSession, - organizationDto.getUuid(), - new DefaultTemplates().setProjectUuid(permissionTemplateDto.getUuid())); - } - - private void insertGroupPermission(DbSession dbSession, PermissionTemplateDto template, String permission, @Nullable GroupDto group) { - dbClient.permissionTemplateDao().insertGroupPermission(dbSession, template.getId(), group == null ? null : group.getId(), permission); - } - - private void insertQualityProfiles(DbSession dbSession, DbSession batchDbSession, OrganizationDto organization) { - Map<QProfileName, BuiltInQProfile> builtInsPerName = builtInQProfileRepository.get().stream() - .collect(uniqueIndex(BuiltInQProfile::getQProfileName)); - - List<DefaultQProfileDto> defaults = new ArrayList<>(); - dbClient.qualityProfileDao().selectBuiltInRuleProfiles(dbSession).forEach(rulesProfile -> { - OrgQProfileDto dto = new OrgQProfileDto() - .setOrganizationUuid(organization.getUuid()) - .setRulesProfileUuid(rulesProfile.getKee()) - .setUuid(uuidFactory.create()); - - QProfileName name = new QProfileName(rulesProfile.getLanguage(), rulesProfile.getName()); - BuiltInQProfile builtIn = builtInsPerName.get(name); - if (builtIn == null || builtIn.isDefault()) { - // If builtIn == null, the plugin has been removed - // rows of table default_qprofiles must be inserted after org_qprofiles - // in order to benefit from batch SQL inserts - defaults.add(new DefaultQProfileDto() - .setQProfileUuid(dto.getUuid()) - .setOrganizationUuid(organization.getUuid()) - .setLanguage(rulesProfile.getLanguage())); - } - - dbClient.qualityProfileDao().insert(batchDbSession, dto); - }); - - defaults.forEach(defaultQProfileDto -> dbClient.defaultQProfileDao().insertOrUpdate(dbSession, defaultQProfileDto)); - } - - /** - * Owners group has an hard coded name, a description based on the organization's name and has all global permissions. - */ - private GroupDto insertOwnersGroup(DbSession dbSession, OrganizationDto organization) { - GroupDto group = dbClient.groupDao().insert(dbSession, new GroupDto() - .setOrganizationUuid(organization.getUuid()) - .setName(OWNERS_GROUP_NAME) - .setDescription(OWNERS_GROUP_DESCRIPTION)); - permissionService.getAllOrganizationPermissions().forEach(p -> addPermissionToGroup(dbSession, group, p)); - return group; - } - - private void addPermissionToGroup(DbSession dbSession, GroupDto group, OrganizationPermission permission) { - dbClient.groupPermissionDao().insert( - dbSession, - new GroupPermissionDto() - .setOrganizationUuid(group.getOrganizationUuid()) - .setGroupId(group.getId()) - .setRole(permission.getKey())); - } - - private void addCurrentUserToGroup(DbSession dbSession, GroupDto group, int createUserId) { - dbClient.userGroupDao().insert( - dbSession, - new UserGroupDto().setGroupId(group.getId()).setUserId(createUserId)); - } - - private void insertOrganizationMember(DbSession dbSession, OrganizationDto organizationDto, int userId) { - dbClient.organizationMemberDao().insert(dbSession, new OrganizationMemberDto() - .setOrganizationUuid(organizationDto.getUuid()) - .setUserId(userId)); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationValidation.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationValidation.java deleted file mode 100644 index 37fb7eb5758..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationValidation.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - -public interface OrganizationValidation { - int KEY_MIN_LENGTH = 1; - int KEY_MAX_LENGTH = 255; - int NAME_MIN_LENGTH = 1; - int NAME_MAX_LENGTH = 255; - int DESCRIPTION_MAX_LENGTH = 256; - int URL_MAX_LENGTH = 256; - - /** - * Ensures the specified argument is a valid key by failing with an exception if it is not so. - * <p> - * A valid key is non null and its length is between {@link #KEY_MIN_LENGTH} and {@link #KEY_MAX_LENGTH}. - * </p> - * - * @return the argument - * - * @throws NullPointerException if argument is {@code null}. - * @throws IllegalArgumentException if argument is not a valid key. - */ - String checkKey(String keyCandidate); - - /** - * Ensures the specified argument is a valid name by failing with an exception if it is not so. - * <p> - * A valid name is non null and its length is between {@link #NAME_MIN_LENGTH} and {@link #NAME_MAX_LENGTH}. - * </p> - * - * @return the argument - * - * @throws NullPointerException if argument is {@code null}. - * @throws IllegalArgumentException if argument is not a valid name. - */ - String checkName(String nameCandidate); - - /** - * Ensures the specified argument is either {@code null}, empty or a valid description by failing with an exception - * if it is not so. - * <p> - * The length of a valid url can't be more than {@link #DESCRIPTION_MAX_LENGTH 256}. - * </p> - * - * @return the argument - * - * @throws IllegalArgumentException if argument is not a valid description. - */ - @CheckForNull - String checkDescription(@Nullable String descriptionCandidate); - - /** - * Ensures the specified argument is either {@code null}, empty or a valid URL by failing with an exception if it is - * not so. - * <p> - * The length of a valid URL can't be more than {@link #URL_MAX_LENGTH 256}. - * </p> - * - * @return the argument - * - * @throws IllegalArgumentException if argument is not a valid url. - */ - @CheckForNull - String checkUrl(@Nullable String urlCandidate); - - /** - * Ensures the specified argument is either {@code null}, empty or a valid avatar URL by failing with an exception if - * it is not so. - * <p> - * The length of a valid avatar URL can't be more than {@link #URL_MAX_LENGTH 256}. - * </p> - * - * @return the argument - * - * @throws IllegalArgumentException if argument is not a valid avatar url. - */ - @CheckForNull - String checkAvatar(@Nullable String avatarCandidate); - - /** - * Transforms the specified string into a valid key. - * - * @see #checkKey(String) - */ - String generateKeyFrom(String source); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationValidationImpl.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationValidationImpl.java deleted file mode 100644 index ab6e0c4fb87..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationValidationImpl.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Objects.requireNonNull; -import static org.sonar.core.util.Slug.slugify; - -public class OrganizationValidationImpl implements OrganizationValidation { - - @Override - public String checkKey(String keyCandidate) { - requireNonNull(keyCandidate, "key can't be null"); - checkArgument(keyCandidate.length() >= KEY_MIN_LENGTH, "Key must not be empty"); - checkArgument(keyCandidate.length() <= KEY_MAX_LENGTH, "Key '%s' must be at most %s chars long", keyCandidate, KEY_MAX_LENGTH); - checkArgument(slugify(keyCandidate).equals(keyCandidate), "Key '%s' contains at least one invalid char", keyCandidate); - - return keyCandidate; - } - - @Override - public String checkName(String nameCandidate) { - requireNonNull(nameCandidate, "name can't be null"); - - checkArgument(nameCandidate.length() >= NAME_MIN_LENGTH, "Name must not be empty"); - checkArgument(nameCandidate.length() <= NAME_MAX_LENGTH, "Name '%s' must be at most %s chars long", nameCandidate, NAME_MAX_LENGTH); - - return nameCandidate; - } - - @Override - public String checkDescription(@Nullable String descriptionCandidate) { - checkParamMaxLength(descriptionCandidate, "Description", DESCRIPTION_MAX_LENGTH); - - return descriptionCandidate; - } - - @Override - public String checkUrl(@Nullable String urlCandidate) { - checkParamMaxLength(urlCandidate, "Url", URL_MAX_LENGTH); - - return urlCandidate; - } - - @Override - public String checkAvatar(@Nullable String avatarCandidate) { - checkParamMaxLength(avatarCandidate, "Avatar", URL_MAX_LENGTH); - - return avatarCandidate; - } - - @CheckForNull - private static void checkParamMaxLength(@Nullable String value, String label, int maxLength) { - if (value != null) { - checkArgument(value.length() <= maxLength, "%s '%s' must be at most %s chars long", label, value, maxLength); - } - } - - @Override - public String generateKeyFrom(String source) { - return slugify(source); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/organization/package-info.java deleted file mode 100644 index 0b32d7b6139..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.organization; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/GroupId.java b/server/sonar-server/src/main/java/org/sonar/server/permission/GroupId.java deleted file mode 100644 index 0faba895d4a..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/GroupId.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.permission; - -import javax.annotation.concurrent.Immutable; -import org.sonar.db.user.GroupDto; - -import static java.util.Objects.requireNonNull; - -/** - * Reference to a user group, as used internally by the backend. It does - * not support reference to virtual groups "anyone". - * - * @see GroupWsRef - * @see GroupIdOrAnyone - */ -@Immutable -public class GroupId { - - private final int id; - private final String organizationUuid; - - private GroupId(String organizationUuid, int id) { - this.id = id; - this.organizationUuid = requireNonNull(organizationUuid); - } - - public int getId() { - return id; - } - - public String getOrganizationUuid() { - return organizationUuid; - } - - public static GroupId from(GroupDto dto) { - return new GroupId(dto.getOrganizationUuid(), dto.getId()); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/GroupIdOrAnyone.java b/server/sonar-server/src/main/java/org/sonar/server/permission/GroupIdOrAnyone.java deleted file mode 100644 index 4a6add59eb6..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/GroupIdOrAnyone.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.permission; - -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import org.sonar.db.user.GroupDto; - -import static java.util.Objects.requireNonNull; - -/** - * Reference to a user group, as used internally by the backend. Contrary to - * {@link GroupId}, it supports reference to virtual groups "anyone". In these - * cases {@link #getId()} returns {@code null} - * - * @see GroupId - */ -@Immutable -public class GroupIdOrAnyone { - - private final Integer id; - private final String organizationUuid; - - private GroupIdOrAnyone(String organizationUuid, @Nullable Integer id) { - this.id = id; - this.organizationUuid = requireNonNull(organizationUuid); - } - - public boolean isAnyone() { - return id == null; - } - - @CheckForNull - public Integer getId() { - return id; - } - - public String getOrganizationUuid() { - return organizationUuid; - } - - public static GroupIdOrAnyone from(GroupDto dto) { - return new GroupIdOrAnyone(dto.getOrganizationUuid(), dto.getId()); - } - - public static GroupIdOrAnyone forAnyone(String organizationUuid) { - return new GroupIdOrAnyone(organizationUuid, null); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/GroupPermissionChanger.java b/server/sonar-server/src/main/java/org/sonar/server/permission/GroupPermissionChanger.java index 5e6a0a48dd0..947310f2274 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/GroupPermissionChanger.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/GroupPermissionChanger.java @@ -21,16 +21,17 @@ package org.sonar.server.permission; import java.util.List; import java.util.Optional; +import org.sonar.core.permission.GlobalPermissions; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.permission.GroupPermissionDto; +import static java.lang.String.format; import static org.sonar.api.web.UserRole.PUBLIC_PERMISSIONS; import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; +import static org.sonar.server.exceptions.BadRequestException.checkRequest; import static org.sonar.server.permission.PermissionChange.Operation.ADD; import static org.sonar.server.permission.PermissionChange.Operation.REMOVE; -import static org.sonar.server.permission.RequestValidator.validateNotAnyoneAndAdminPermission; -import static org.sonar.server.exceptions.BadRequestException.checkRequest; public class GroupPermissionChanger { @@ -117,6 +118,11 @@ public class GroupPermissionChanger { return true; } + private static void validateNotAnyoneAndAdminPermission(String permission, GroupIdOrAnyone group) { + checkRequest(!GlobalPermissions.SYSTEM_ADMIN.equals(permission) || !group.isAnyone(), + format("It is not possible to add the '%s' permission to group 'Anyone'.", permission)); + } + private boolean removePermission(DbSession dbSession, GroupPermissionChange change) { if (!loadExistingPermissions(dbSession, change).contains(change.getPermission())) { return false; diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionPrivilegeChecker.java b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionPrivilegeChecker.java deleted file mode 100644 index 64243e1355b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionPrivilegeChecker.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.permission; - -import java.util.Optional; -import org.sonar.api.web.UserRole; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.server.user.UserSession; - -import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; - -public class PermissionPrivilegeChecker { - private PermissionPrivilegeChecker() { - // static methods only - } - - public static void checkGlobalAdmin(UserSession userSession, String organizationUuid) { - userSession - .checkLoggedIn() - .checkPermission(OrganizationPermission.ADMINISTER, organizationUuid); - } - - /** - * Checks that user is administrator of the specified project, or of the specified organization if project is not - * defined. - * @throws org.sonar.server.exceptions.ForbiddenException if user is not administrator - */ - public static void checkProjectAdmin(UserSession userSession, String organizationUuid, Optional<ProjectId> projectId) { - userSession.checkLoggedIn(); - - if (userSession.hasPermission(OrganizationPermission.ADMINISTER, organizationUuid)) { - return; - } - - if (projectId.isPresent()) { - userSession.checkComponentUuidPermission(UserRole.ADMIN, projectId.get().getUuid()); - } else { - throw insufficientPrivilegesException(); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionService.java b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionService.java deleted file mode 100644 index fdfe77d7383..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionService.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.permission; - -import java.util.List; -import org.sonar.db.permission.OrganizationPermission; - -public interface PermissionService { - - List<OrganizationPermission> getAllOrganizationPermissions(); - List<String> getAllProjectPermissions(); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionServiceImpl.java b/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionServiceImpl.java deleted file mode 100644 index a7278d4ed20..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/PermissionServiceImpl.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.permission; - -import com.google.common.collect.ImmutableList; -import java.util.List; -import javax.annotation.concurrent.Immutable; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.ResourceTypes; -import org.sonar.api.web.UserRole; -import org.sonar.db.permission.OrganizationPermission; - -import static java.util.stream.Collectors.toList; - -@Immutable -public class PermissionServiceImpl implements PermissionService { - - private static final List<String> ALL_PROJECT_PERMISSIONS = ImmutableList.of( - UserRole.ADMIN, UserRole.CODEVIEWER, UserRole.ISSUE_ADMIN, UserRole.SECURITYHOTSPOT_ADMIN, UserRole.SCAN, UserRole.USER); - - private static final List<OrganizationPermission> ALL_GLOBAL_PERMISSIONS = ImmutableList.copyOf(OrganizationPermission.values()); - - private final List<OrganizationPermission> globalPermissions; - private final List<String> projectPermissions; - - public PermissionServiceImpl(ResourceTypes resourceTypes) { - globalPermissions = ImmutableList.copyOf(ALL_GLOBAL_PERMISSIONS.stream() - .filter(s -> !s.equals(OrganizationPermission.APPLICATION_CREATOR) || resourceTypes.isQualifierPresent(Qualifiers.APP)) - .filter(s -> !s.equals(OrganizationPermission.PORTFOLIO_CREATOR) || resourceTypes.isQualifierPresent(Qualifiers.VIEW)) - .collect(toList())); - projectPermissions = ImmutableList.copyOf(ALL_PROJECT_PERMISSIONS.stream() - .filter(s -> !s.equals(OrganizationPermission.APPLICATION_CREATOR.getKey()) || resourceTypes.isQualifierPresent(Qualifiers.APP)) - .filter(s -> !s.equals(OrganizationPermission.PORTFOLIO_CREATOR.getKey()) || resourceTypes.isQualifierPresent(Qualifiers.VIEW)) - .collect(toList())); - } - - /** - * Return an immutable Set of all organization permissions - */ - @Override - public List<OrganizationPermission> getAllOrganizationPermissions() { - return globalPermissions; - } - - /** - * Return an immutable Set of all project permissions - */ - @Override - public List<String> getAllProjectPermissions() { - return projectPermissions; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/ProjectId.java b/server/sonar-server/src/main/java/org/sonar/server/permission/ProjectId.java deleted file mode 100644 index 0fb1941e3b8..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/ProjectId.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.permission; - -import javax.annotation.concurrent.Immutable; -import org.sonar.db.component.ComponentDto; - -import static java.util.Objects.requireNonNull; - -/** - * Reference to a project by its db id or uuid. The field "id" should - * be removed as soon as backend is fully based on uuids. - * - */ -@Immutable -public class ProjectId { - - private final long id; - private final String uuid; - private final boolean isPrivate; - - public ProjectId(ComponentDto project) { - this.id = requireNonNull(project.getId()); - this.uuid = requireNonNull(project.uuid()); - this.isPrivate = project.isPrivate(); - } - - public long getId() { - return id; - } - - public String getUuid() { - return uuid; - } - - public boolean isPrivate() { - return isPrivate; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/RequestValidator.java b/server/sonar-server/src/main/java/org/sonar/server/permission/RequestValidator.java deleted file mode 100644 index 8fe5e6ba069..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/RequestValidator.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.permission; - -import com.google.common.base.Joiner; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; -import java.util.stream.Collectors; -import javax.annotation.Nullable; -import org.sonar.api.resources.ResourceType; -import org.sonar.api.resources.ResourceTypes; -import org.sonar.core.permission.GlobalPermissions; -import org.sonar.server.exceptions.BadRequestException; - -import static com.google.common.base.Strings.isNullOrEmpty; -import static java.lang.String.format; -import static org.sonar.server.exceptions.BadRequestException.checkRequest; -import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PERMISSION; -import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_KEY_PATTERN; -import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_QUALIFIER; - -public class RequestValidator { - public static final String MSG_TEMPLATE_WITH_SAME_NAME = "A template with the name '%s' already exists (case insensitive)."; - private final PermissionService permissionService; - private final String allProjectsPermissionsOnOneLine; - - public RequestValidator(PermissionService permissionService) { - this.permissionService = permissionService; - allProjectsPermissionsOnOneLine = Joiner.on(", ").join(permissionService.getAllProjectPermissions()); - } - - public String validateProjectPermission(String permission) { - BadRequestException.checkRequest(permissionService.getAllProjectPermissions().contains(permission), - String.format("The '%s' parameter for project permissions must be one of %s. '%s' was passed.", PARAM_PERMISSION, - allProjectsPermissionsOnOneLine, permission)); - return permission; - } - - public static void validateGlobalPermission(String permission) { - checkRequest(GlobalPermissions.ALL.contains(permission), - format("The '%s' parameter for global permissions must be one of %s. '%s' was passed.", PARAM_PERMISSION, GlobalPermissions.ALL_ON_ONE_LINE, permission)); - } - - public static void validateNotAnyoneAndAdminPermission(String permission, GroupIdOrAnyone group) { - checkRequest(!GlobalPermissions.SYSTEM_ADMIN.equals(permission) || !group.isAnyone(), - format("It is not possible to add the '%s' permission to group 'Anyone'.", permission)); - } - - public static void validateQualifier(@Nullable String qualifier, ResourceTypes resourceTypes) { - if (qualifier == null) { - return; - } - Set<String> rootQualifiers = resourceTypes.getRoots().stream() - .map(ResourceType::getQualifier) - .collect(Collectors.toSet()); - checkRequest(rootQualifiers.contains(qualifier), - format("The '%s' parameter must be one of %s. '%s' was passed.", PARAM_QUALIFIER, rootQualifiers, qualifier)); - } - - public static void validateProjectPattern(@Nullable String projectPattern) { - if (isNullOrEmpty(projectPattern)) { - return; - } - - try { - Pattern.compile(projectPattern); - } catch (PatternSyntaxException e) { - throw BadRequestException.create(format("The '%s' parameter must be a valid Java regular expression. '%s' was passed", PARAM_PROJECT_KEY_PATTERN, projectPattern)); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/UserId.java b/server/sonar-server/src/main/java/org/sonar/server/permission/UserId.java deleted file mode 100644 index 4c859a29322..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/UserId.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.permission; - -import javax.annotation.concurrent.Immutable; -import org.sonar.db.user.UserDto; - -import static java.util.Objects.requireNonNull; - -/** - * Reference a user by his technical (db) id or functional login. - * This is temporary class as long as services and DAOs do not - * use only technical id. - */ -@Immutable -public class UserId { - - private final int id; - private final String login; - - public UserId(int userId, String login) { - this.id = userId; - this.login = requireNonNull(login); - } - - public int getId() { - return id; - } - - public String getLogin() { - return login; - } - - public static UserId from(UserDto dto) { - return new UserId(dto.getId(), dto.getLogin()); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java deleted file mode 100644 index 22f3e451a97..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.platform; - -import org.sonar.core.platform.ComponentContainer; - -public interface Platform { - void doStart(); - - Status status(); - - ComponentContainer getContainer(); - - enum Status { - BOOTING, SAFEMODE, STARTING, UP - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotification.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotification.java deleted file mode 100644 index ee7c4cb8b54..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotification.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import org.sonar.api.notifications.Notification; - -public class BuiltInQPChangeNotification extends Notification { - static final String TYPE = "built-in-quality-profiles"; - - public BuiltInQPChangeNotification() { - super(TYPE); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationBuilder.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationBuilder.java deleted file mode 100644 index 4115d0641ba..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationBuilder.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.IntStream; -import org.sonar.api.notifications.Notification; - -import static com.google.common.base.Preconditions.checkState; -import static java.lang.Integer.parseInt; -import static java.lang.Long.parseLong; -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; - -public class BuiltInQPChangeNotificationBuilder { - - private static final String NUMBER_OF_PROFILES = "numberOfProfiles"; - private static final String PROFILE_NAME = ".profileName"; - private static final String LANGUAGE_KEY = ".languageKey"; - private static final String LANGUAGE_NAME = ".languageName"; - private static final String NEW_RULES = ".newRules"; - private static final String UPDATED_RULES = ".updatedRules"; - private static final String REMOVED_RULES = ".removedRules"; - private static final String START_DATE = ".startDate"; - private static final String END_DATE = ".endDate"; - - private final List<Profile> profiles = new ArrayList<>(); - - public BuiltInQPChangeNotificationBuilder addProfile(Profile profile) { - profiles.add(profile); - return this; - } - - public BuiltInQPChangeNotification build() { - BuiltInQPChangeNotification notification = new BuiltInQPChangeNotification(); - notification.setFieldValue(NUMBER_OF_PROFILES, String.valueOf(profiles.size())); - AtomicInteger count = new AtomicInteger(); - profiles.forEach(profile -> { - int index = count.getAndIncrement(); - notification.setFieldValue(index + PROFILE_NAME, profile.getProfileName()); - notification.setFieldValue(index + LANGUAGE_KEY, profile.getLanguageKey()); - notification.setFieldValue(index + LANGUAGE_NAME, profile.getLanguageName()); - notification.setFieldValue(index + NEW_RULES, String.valueOf(profile.getNewRules())); - notification.setFieldValue(index + UPDATED_RULES, String.valueOf(profile.getUpdatedRules())); - notification.setFieldValue(index + REMOVED_RULES, String.valueOf(profile.getRemovedRules())); - notification.setFieldValue(index + START_DATE, String.valueOf(profile.getStartDate())); - notification.setFieldValue(index + END_DATE, String.valueOf(profile.getEndDate())); - }); - return notification; - } - - public static BuiltInQPChangeNotificationBuilder parse(Notification notification) { - checkState(BuiltInQPChangeNotification.TYPE.equals(notification.getType()), - "Expected notification of type %s but got %s", BuiltInQPChangeNotification.TYPE, notification.getType()); - BuiltInQPChangeNotificationBuilder notif = new BuiltInQPChangeNotificationBuilder(); - String numberOfProfilesText = notification.getFieldValue(NUMBER_OF_PROFILES); - checkState(numberOfProfilesText != null, "Could not read the built-in quality profile notification"); - Integer numberOfProfiles = Integer.valueOf(numberOfProfilesText); - IntStream.rangeClosed(0, numberOfProfiles - 1) - .mapToObj(index -> Profile.newBuilder() - .setProfileName(getNonNullFieldValue(notification, index + PROFILE_NAME)) - .setLanguageKey(getNonNullFieldValue(notification, index + LANGUAGE_KEY)) - .setLanguageName(getNonNullFieldValue(notification, index + LANGUAGE_NAME)) - .setNewRules(parseInt(getNonNullFieldValue(notification, index + NEW_RULES))) - .setUpdatedRules(parseInt(getNonNullFieldValue(notification, index + UPDATED_RULES))) - .setRemovedRules(parseInt(getNonNullFieldValue(notification, index + REMOVED_RULES))) - .setStartDate(parseLong(getNonNullFieldValue(notification, index + START_DATE))) - .setEndDate(parseLong(getNonNullFieldValue(notification, index + END_DATE))) - .build()) - .forEach(notif::addProfile); - return notif; - } - - private static String getNonNullFieldValue(Notification notification, String key) { - String value = notification.getFieldValue(key); - return requireNonNull(value, format("Notification field '%s' is null", key)); - } - - public List<Profile> getProfiles() { - return profiles; - } - - public static class Profile { - private final String profileName; - private final String languageKey; - private final String languageName; - private final int newRules; - private final int updatedRules; - private final int removedRules; - private final long startDate; - private final long endDate; - - public Profile(Builder builder) { - this.profileName = builder.profileName; - this.languageKey = builder.languageKey; - this.languageName = builder.languageName; - this.newRules = builder.newRules; - this.updatedRules = builder.updatedRules; - this.removedRules = builder.removedRules; - this.startDate = builder.startDate; - this.endDate = builder.endDate; - } - - public String getProfileName() { - return profileName; - } - - public String getLanguageKey() { - return languageKey; - } - - public String getLanguageName() { - return languageName; - } - - public int getNewRules() { - return newRules; - } - - public int getUpdatedRules() { - return updatedRules; - } - - public int getRemovedRules() { - return removedRules; - } - - public long getStartDate() { - return startDate; - } - - public long getEndDate() { - return endDate; - } - - public static Builder newBuilder() { - return new Builder(); - } - - public static class Builder { - private String profileName; - private String languageKey; - private String languageName; - private int newRules; - private int updatedRules; - private int removedRules; - private long startDate; - private long endDate; - - private Builder() { - } - - public Builder setLanguageKey(String languageKey) { - this.languageKey = requireNonNull(languageKey, "languageKEy should not be null"); - return this; - } - - public Builder setLanguageName(String languageName) { - this.languageName = requireNonNull(languageName, "languageName should not be null"); - return this; - } - - public Builder setProfileName(String profileName) { - this.profileName = requireNonNull(profileName, "profileName should not be null"); - return this; - } - - public Builder setNewRules(int newRules) { - checkState(newRules >= 0, "newRules should not be negative"); - this.newRules = newRules; - return this; - } - - public Builder setUpdatedRules(int updatedRules) { - checkState(updatedRules >= 0, "updatedRules should not be negative"); - this.updatedRules = updatedRules; - return this; - } - - public Builder setRemovedRules(int removedRules) { - checkState(removedRules >= 0, "removedRules should not be negative"); - this.removedRules = removedRules; - return this; - } - - public Builder setStartDate(long startDate) { - this.startDate = startDate; - return this; - } - - public Builder setEndDate(long endDate) { - this.endDate = endDate; - return this; - } - - public Profile build() { - return new Profile(this); - } - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationHandler.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationHandler.java deleted file mode 100644 index 302ba14ad9b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationHandler.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.Collection; -import java.util.Optional; -import java.util.Set; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.server.notification.EmailNotificationHandler; -import org.sonar.server.notification.NotificationDispatcherMetadata; -import org.sonar.server.notification.email.EmailNotificationChannel; -import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest; - -import static org.sonar.core.util.stream.MoreCollectors.toSet; - -public class BuiltInQPChangeNotificationHandler extends EmailNotificationHandler<BuiltInQPChangeNotification> { - private final DbClient dbClient; - - public BuiltInQPChangeNotificationHandler(DbClient dbClient, EmailNotificationChannel emailNotificationChannel) { - super(emailNotificationChannel); - this.dbClient = dbClient; - } - - @Override - public Optional<NotificationDispatcherMetadata> getMetadata() { - return Optional.empty(); - } - - @Override - public Class<BuiltInQPChangeNotification> getNotificationClass() { - return BuiltInQPChangeNotification.class; - } - - @Override - public Set<EmailDeliveryRequest> toEmailDeliveryRequests(Collection<BuiltInQPChangeNotification> notifications) { - try (DbSession session = dbClient.openSession(false)) { - return dbClient.authorizationDao() - .selectQualityProfileAdministratorLogins(session) - .stream() - .flatMap(t -> notifications.stream().map(notification -> new EmailDeliveryRequest(t.getEmail(), notification))) - .collect(toSet()); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationTemplate.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationTemplate.java deleted file mode 100644 index ccc644b02a1..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationTemplate.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Comparator; -import java.util.Date; -import javax.annotation.CheckForNull; -import org.sonar.api.notifications.Notification; -import org.sonar.api.platform.Server; -import org.sonar.server.issue.notification.EmailMessage; -import org.sonar.server.issue.notification.EmailTemplate; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.sonar.api.utils.DateUtils.formatDate; -import static org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.Profile; -import static org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.parse; - -public class BuiltInQPChangeNotificationTemplate implements EmailTemplate { - - private final Server server; - - public BuiltInQPChangeNotificationTemplate(Server server) { - this.server = server; - } - - @Override - @CheckForNull - public EmailMessage format(Notification notification) { - if (!BuiltInQPChangeNotification.TYPE.equals(notification.getType())) { - return null; - } - - BuiltInQPChangeNotificationBuilder profilesNotification = parse(notification); - StringBuilder message = new StringBuilder("The following built-in profiles have been updated:\n\n"); - profilesNotification.getProfiles().stream() - .sorted(Comparator.comparing(Profile::getLanguageName).thenComparing(Profile::getProfileName)) - .forEach(profile -> { - message.append("\"") - .append(profile.getProfileName()) - .append("\" - ") - .append(profile.getLanguageName()) - .append(": ") - .append(server.getPublicRootUrl()).append("/profiles/changelog?language=") - .append(profile.getLanguageKey()) - .append("&name=") - .append(encode(profile.getProfileName())) - .append("&since=") - .append(formatDate(new Date(profile.getStartDate()))) - .append("&to=") - .append(formatDate(new Date(profile.getEndDate()))) - .append("\n"); - int newRules = profile.getNewRules(); - if (newRules > 0) { - message.append(" ").append(newRules).append(" new rule") - .append(plural(newRules)) - .append('\n'); - } - int updatedRules = profile.getUpdatedRules(); - if (updatedRules > 0) { - message.append(" ").append(updatedRules).append(" rule") - .append(updatedRules > 1 ? "s have been updated" : " has been updated") - .append("\n"); - } - int removedRules = profile.getRemovedRules(); - if (removedRules > 0) { - message.append(" ").append(removedRules).append(" rule") - .append(plural(removedRules)) - .append(" removed\n"); - } - message.append("\n"); - }); - - message.append("This is a good time to review your quality profiles and update them to benefit from the latest evolutions: "); - message.append(server.getPublicRootUrl()).append("/profiles"); - - // And finally return the email that will be sent - return new EmailMessage() - .setMessageId(BuiltInQPChangeNotification.TYPE) - .setSubject("Built-in quality profiles have been updated") - .setPlainTextMessage(message.toString()); - } - - private static String plural(int count) { - return count > 1 ? "s" : ""; - } - - public String encode(String text) { - try { - return URLEncoder.encode(text, UTF_8.name()); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(String.format("Cannot encode %s", text), e); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfile.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfile.java deleted file mode 100644 index 96750f5976c..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfile.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import com.google.common.collect.ImmutableList; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.concurrent.Immutable; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; - -/** - * Represent a Quality Profile as computed from {@link BuiltInQualityProfilesDefinition} provided by installed plugins. - */ -@Immutable -public final class BuiltInQProfile { - private final QProfileName qProfileName; - private final boolean isDefault; - private final List<ActiveRule> activeRules; - - private BuiltInQProfile(Builder builder) { - this.qProfileName = new QProfileName(builder.language, builder.name); - this.isDefault = builder.declaredDefault || builder.computedDefault; - this.activeRules = ImmutableList.copyOf(builder.activeRules); - } - - public String getName() { - return qProfileName.getName(); - } - - public String getLanguage() { - return qProfileName.getLanguage(); - } - - public QProfileName getQProfileName() { - return qProfileName; - } - - public boolean isDefault() { - return isDefault; - } - - public List<ActiveRule> getActiveRules() { - return activeRules; - } - - static final class ActiveRule { - private final int ruleId; - private final BuiltInQualityProfilesDefinition.BuiltInActiveRule builtIn; - - ActiveRule(int ruleId, BuiltInQualityProfilesDefinition.BuiltInActiveRule builtIn) { - this.ruleId = ruleId; - this.builtIn = builtIn; - } - - public int getRuleId() { - return ruleId; - } - - public RuleKey getRuleKey() { - return RuleKey.of(builtIn.repoKey(), builtIn.ruleKey()); - } - - public BuiltInQualityProfilesDefinition.BuiltInActiveRule getBuiltIn() { - return builtIn; - } - } - - static final class Builder { - private String language; - private String name; - private boolean declaredDefault; - private boolean computedDefault; - private final List<ActiveRule> activeRules = new ArrayList<>(); - - public Builder setLanguage(String language) { - this.language = language; - return this; - } - - Builder setName(String name) { - this.name = name; - return this; - } - - String getName() { - return name; - } - - Builder setDeclaredDefault(boolean declaredDefault) { - this.declaredDefault = declaredDefault; - return this; - } - - boolean isDeclaredDefault() { - return declaredDefault; - } - - Builder setComputedDefault(boolean flag) { - computedDefault = flag; - return this; - } - - Builder addRule(BuiltInQualityProfilesDefinition.BuiltInActiveRule rule, int ruleId) { - this.activeRules.add(new ActiveRule(ruleId, rule)); - return this; - } - - Builder addRule(ActiveRule activeRule) { - this.activeRules.add(activeRule); - return this; - } - - BuiltInQProfile build() { - return new BuiltInQProfile(this); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridge.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridge.java deleted file mode 100644 index 58feb3b73f1..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridge.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import com.google.common.collect.ImmutableList; -import java.util.List; -import org.sonar.api.profiles.ProfileDefinition; -import org.sonar.api.profiles.RulesProfile; -import org.sonar.api.rules.ActiveRuleParam; -import org.sonar.api.rules.RulePriority; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.api.utils.ValidationMessages; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.api.utils.log.Profiler; - -import static java.lang.String.format; - -/** - * Bridge between deprecated {@link ProfileDefinition} API and new {@link BuiltInQualityProfilesDefinition} - */ -public class BuiltInQProfileDefinitionsBridge implements BuiltInQualityProfilesDefinition { - private static final Logger LOGGER = Loggers.get(BuiltInQProfileDefinitionsBridge.class); - - private final List<ProfileDefinition> definitions; - - /** - * Requires for pico container when no {@link ProfileDefinition} is defined at all - */ - public BuiltInQProfileDefinitionsBridge() { - this(new ProfileDefinition[0]); - } - - public BuiltInQProfileDefinitionsBridge(ProfileDefinition... definitions) { - this.definitions = ImmutableList.copyOf(definitions); - } - - @Override - public void define(Context context) { - Profiler profiler = Profiler.create(Loggers.get(getClass())); - for (ProfileDefinition definition : definitions) { - profiler.start(); - ValidationMessages validation = ValidationMessages.create(); - RulesProfile profile = definition.createProfile(validation); - validation.log(LOGGER); - if (profile == null) { - profiler.stopDebug(format("Loaded definition %s that return no profile", definition)); - } else { - if (!validation.hasErrors()) { - define(context, profile); - } - profiler.stopDebug(format("Loaded deprecated profile definition %s for language %s", profile.getName(), profile.getLanguage())); - } - } - } - - private static void define(Context context, RulesProfile profile) { - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage()) - .setDefault(profile.getDefaultProfile().booleanValue()); - - for (org.sonar.api.rules.ActiveRule ar : profile.getActiveRules()) { - NewBuiltInActiveRule newActiveRule = newQp.activateRule(ar.getRepositoryKey(), ar.getRuleKey()); - RulePriority overriddenSeverity = ar.getOverriddenSeverity(); - if (overriddenSeverity != null) { - newActiveRule.overrideSeverity(overriddenSeverity.name()); - } - for (ActiveRuleParam param : ar.getActiveRuleParams()) { - newActiveRule.overrideParam(param.getKey(), param.getValue()); - } - } - newQp.done(); - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsert.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsert.java deleted file mode 100644 index 52d52009f03..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsert.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import org.sonar.db.DbSession; - -public interface BuiltInQProfileInsert { - /** - * Persist a new built-in profile and associate it to all existing organizations. - * Db sessions are committed and Elasticsearch indices are updated.. - */ - void create(DbSession session, DbSession batchSession, BuiltInQProfile builtInQProfile); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java deleted file mode 100644 index fab592130f1..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.api.server.rule.RuleParamType; -import org.sonar.api.utils.System2; -import org.sonar.core.util.UuidFactory; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.qualityprofile.ActiveRuleDto; -import org.sonar.db.qualityprofile.ActiveRuleKey; -import org.sonar.db.qualityprofile.ActiveRuleParamDto; -import org.sonar.db.qualityprofile.DefaultQProfileDto; -import org.sonar.db.qualityprofile.OrgQProfileDto; -import org.sonar.db.qualityprofile.RulesProfileDto; -import org.sonar.db.rule.RuleDefinitionDto; -import org.sonar.db.rule.RuleParamDto; -import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; -import org.sonar.server.util.TypeValidations; - -import static com.google.common.base.MoreObjects.firstNonNull; -import static com.google.common.collect.Lists.newArrayList; -import static java.util.Objects.requireNonNull; - -public class BuiltInQProfileInsertImpl implements BuiltInQProfileInsert { - private final DbClient dbClient; - private final System2 system2; - private final UuidFactory uuidFactory; - private final TypeValidations typeValidations; - private final ActiveRuleIndexer activeRuleIndexer; - private RuleRepository ruleRepository; - - public BuiltInQProfileInsertImpl(DbClient dbClient, System2 system2, UuidFactory uuidFactory, TypeValidations typeValidations, ActiveRuleIndexer activeRuleIndexer) { - this.dbClient = dbClient; - this.system2 = system2; - this.uuidFactory = uuidFactory; - this.typeValidations = typeValidations; - this.activeRuleIndexer = activeRuleIndexer; - } - - @Override - public void create(DbSession dbSession, DbSession batchDbSession, BuiltInQProfile builtInQProfile) { - initRuleRepository(batchDbSession); - - Date now = new Date(system2.now()); - RulesProfileDto ruleProfile = insertRulesProfile(dbSession, builtInQProfile, now); - - List<ActiveRuleChange> changes = builtInQProfile.getActiveRules() - .stream() - .map(activeRule -> insertActiveRule(dbSession, ruleProfile, activeRule, now.getTime())) - .collect(MoreCollectors.toList()); - - changes.forEach(change -> dbClient.qProfileChangeDao().insert(batchDbSession, change.toDto(null))); - - associateToOrganizations(dbSession, batchDbSession, builtInQProfile, ruleProfile); - - // TODO batch statements should be executed through dbSession - batchDbSession.commit(); - - activeRuleIndexer.commitAndIndex(dbSession, changes); - } - - private void associateToOrganizations(DbSession dbSession, DbSession batchDbSession, BuiltInQProfile builtIn, RulesProfileDto rulesProfileDto) { - List<String> orgUuids = dbClient.organizationDao().selectAllUuids(dbSession); - Set<String> orgUuidsWithoutDefault = dbClient.defaultQProfileDao().selectUuidsOfOrganizationsWithoutDefaultProfile(dbSession, builtIn.getLanguage()); - - List<DefaultQProfileDto> defaults = new ArrayList<>(); - orgUuids.forEach(orgUuid -> { - OrgQProfileDto dto = new OrgQProfileDto() - .setOrganizationUuid(orgUuid) - .setRulesProfileUuid(rulesProfileDto.getKee()) - .setUuid(uuidFactory.create()); - - if (builtIn.isDefault() && orgUuidsWithoutDefault.contains(orgUuid)) { - // rows of table default_qprofiles must be inserted after - // in order to benefit from batch SQL inserts - defaults.add(new DefaultQProfileDto() - .setQProfileUuid(dto.getUuid()) - .setOrganizationUuid(orgUuid) - .setLanguage(builtIn.getLanguage())); - } - - dbClient.qualityProfileDao().insert(batchDbSession, dto); - }); - - defaults.forEach(defaultQProfileDto -> dbClient.defaultQProfileDao().insertOrUpdate(dbSession, defaultQProfileDto)); - } - - private void initRuleRepository(DbSession dbSession) { - if (ruleRepository == null) { - ruleRepository = new RuleRepository(dbClient, dbSession); - } - } - - private RulesProfileDto insertRulesProfile(DbSession dbSession, BuiltInQProfile builtIn, Date now) { - RulesProfileDto dto = new RulesProfileDto() - .setKee(uuidFactory.create()) - .setName(builtIn.getName()) - .setLanguage(builtIn.getLanguage()) - .setIsBuiltIn(true) - .setRulesUpdatedAtAsDate(now); - dbClient.qualityProfileDao().insert(dbSession, dto); - return dto; - } - - private ActiveRuleChange insertActiveRule(DbSession dbSession, RulesProfileDto rulesProfileDto, BuiltInQProfile.ActiveRule activeRule, long now) { - RuleKey ruleKey = activeRule.getRuleKey(); - RuleDefinitionDto ruleDefinitionDto = ruleRepository.getDefinition(ruleKey) - .orElseThrow(() -> new IllegalStateException("RuleDefinition not found for key " + ruleKey)); - - ActiveRuleDto dto = new ActiveRuleDto(); - dto.setProfileId(rulesProfileDto.getId()); - dto.setRuleId(ruleDefinitionDto.getId()); - dto.setKey(ActiveRuleKey.of(rulesProfileDto, ruleDefinitionDto.getKey())); - dto.setSeverity(firstNonNull(activeRule.getBuiltIn().overriddenSeverity(), ruleDefinitionDto.getSeverityString())); - dto.setUpdatedAt(now); - dto.setCreatedAt(now); - dbClient.activeRuleDao().insert(dbSession, dto); - - List<ActiveRuleParamDto> paramDtos = insertActiveRuleParams(dbSession, activeRule, dto); - - ActiveRuleChange change = new ActiveRuleChange(ActiveRuleChange.Type.ACTIVATED, dto, ruleDefinitionDto); - change.setSeverity(dto.getSeverityString()); - paramDtos.forEach(paramDto -> change.setParameter(paramDto.getKey(), paramDto.getValue())); - return change; - } - - private List<ActiveRuleParamDto> insertActiveRuleParams(DbSession session, BuiltInQProfile.ActiveRule activeRule, - ActiveRuleDto activeRuleDto) { - Map<String, String> valuesByParamKey = activeRule.getBuiltIn().overriddenParams() - .stream() - .collect(MoreCollectors.uniqueIndex(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue)); - return ruleRepository.getRuleParams(activeRule.getRuleKey()) - .stream() - .map(param -> { - String activeRuleValue = valuesByParamKey.get(param.getName()); - return createParamDto(param, activeRuleValue == null ? param.getDefaultValue() : activeRuleValue); - }) - .filter(Objects::nonNull) - .peek(paramDto -> dbClient.activeRuleDao().insertParam(session, activeRuleDto, paramDto)) - .collect(MoreCollectors.toList()); - } - - @CheckForNull - private ActiveRuleParamDto createParamDto(RuleParamDto param, @Nullable String value) { - if (value == null) { - return null; - } - ActiveRuleParamDto paramDto = ActiveRuleParamDto.createFor(param); - paramDto.setValue(validateParam(param, value)); - return paramDto; - } - - @CheckForNull - private String validateParam(RuleParamDto ruleParam, String value) { - RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType()); - if (ruleParamType.multiple()) { - List<String> values = newArrayList(Splitter.on(",").split(value)); - typeValidations.validate(values, ruleParamType.type(), ruleParamType.values()); - } else { - typeValidations.validate(value, ruleParamType.type(), ruleParamType.values()); - } - return value; - } - - private static class RuleRepository { - private final Map<RuleKey, RuleDefinitionDto> definitions; - private final Map<RuleKey, Set<RuleParamDto>> params; - - private RuleRepository(DbClient dbClient, DbSession session) { - this.definitions = dbClient.ruleDao().selectAllDefinitions(session) - .stream() - .collect(Collectors.toMap(RuleDefinitionDto::getKey, Function.identity())); - Map<Integer, RuleKey> ruleIdsByKey = definitions.values() - .stream() - .collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getId, RuleDefinitionDto::getKey)); - this.params = new HashMap<>(ruleIdsByKey.size()); - dbClient.ruleDao().selectRuleParamsByRuleKeys(session, definitions.keySet()) - .forEach(ruleParam -> params.compute( - ruleIdsByKey.get(ruleParam.getRuleId()), - (key, value) -> { - if (value == null) { - return ImmutableSet.of(ruleParam); - } - return ImmutableSet.copyOf(Sets.union(value, Collections.singleton(ruleParam))); - })); - } - - private Optional<RuleDefinitionDto> getDefinition(RuleKey ruleKey) { - return Optional.ofNullable(definitions.get(requireNonNull(ruleKey, "RuleKey can't be null"))); - } - - private Set<RuleParamDto> getRuleParams(RuleKey ruleKey) { - Set<RuleParamDto> res = params.get(requireNonNull(ruleKey, "RuleKey can't be null")); - return res == null ? Collections.emptySet() : res; - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileLoader.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileLoader.java deleted file mode 100644 index 9134b3a464b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileLoader.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import org.picocontainer.Startable; - -/** - * Startable added to {@link org.sonar.server.platform.platformlevel.PlatformLevelStartup} responsible for initializing - * {@link BuiltInQProfileRepository}. - */ -public class BuiltInQProfileLoader implements Startable { - private final BuiltInQProfileRepository builtInQProfileRepository; - - public BuiltInQProfileLoader(BuiltInQProfileRepository builtInQProfileRepository) { - this.builtInQProfileRepository = builtInQProfileRepository; - } - - @Override - public void start() { - builtInQProfileRepository.initialize(); - } - - @Override - public void stop() { - // nothing to do - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepository.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepository.java deleted file mode 100644 index b47ff98b3be..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepository.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.List; - -public interface BuiltInQProfileRepository { - /** - * Initializes the Repository. - * - * This method is intended to be called from a startup task - * (see {@link org.sonar.server.platform.platformlevel.PlatformLevelStartup}). - * - * @throws IllegalStateException if called more then once - */ - void initialize(); - - /** - * @return an immutable list - * - * @throws IllegalStateException if {@link #initialize()} has not been called - */ - List<BuiltInQProfile> get(); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImpl.java deleted file mode 100644 index 9473d574a04..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImpl.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import com.google.common.collect.ImmutableList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import javax.annotation.Nullable; -import org.sonar.api.profiles.RulesProfile; -import org.sonar.api.resources.Language; -import org.sonar.api.resources.Languages; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.api.utils.log.Profiler; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.rule.RuleDefinitionDto; - -import static com.google.common.base.Preconditions.checkState; - -public class BuiltInQProfileRepositoryImpl implements BuiltInQProfileRepository { - private static final Logger LOGGER = Loggers.get(BuiltInQProfileRepositoryImpl.class); - private static final String DEFAULT_PROFILE_NAME = "Sonar way"; - - private final DbClient dbClient; - private final Languages languages; - private final List<BuiltInQualityProfilesDefinition> definitions; - private List<BuiltInQProfile> qProfiles; - - /** - * Requires for pico container when no {@link BuiltInQualityProfilesDefinition} is defined at all - */ - public BuiltInQProfileRepositoryImpl(DbClient dbClient, Languages languages) { - this(dbClient, languages, new BuiltInQualityProfilesDefinition[0]); - } - - public BuiltInQProfileRepositoryImpl(DbClient dbClient, Languages languages, BuiltInQualityProfilesDefinition... definitions) { - this.dbClient = dbClient; - this.languages = languages; - this.definitions = ImmutableList.copyOf(definitions); - } - - @Override - public void initialize() { - checkState(qProfiles == null, "initialize must be called only once"); - - Profiler profiler = Profiler.create(LOGGER).startInfo("Load quality profiles"); - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - for (BuiltInQualityProfilesDefinition definition : definitions) { - definition.define(context); - } - Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage = validateAndClean(context); - this.qProfiles = toFlatList(rulesProfilesByLanguage); - ensureAllLanguagesHaveAtLeastOneBuiltInQP(); - profiler.stopDebug(); - } - - @Override - public List<BuiltInQProfile> get() { - checkState(qProfiles != null, "initialize must be called first"); - - return qProfiles; - } - - private void ensureAllLanguagesHaveAtLeastOneBuiltInQP() { - Set<String> languagesWithBuiltInQProfiles = qProfiles.stream().map(BuiltInQProfile::getLanguage).collect(Collectors.toSet()); - Set<String> languagesWithoutBuiltInQProfiles = Arrays.stream(languages.all()) - .map(Language::getKey) - .filter(key -> !languagesWithBuiltInQProfiles.contains(key)) - .collect(Collectors.toSet()); - - checkState(languagesWithoutBuiltInQProfiles.isEmpty(), "The following languages have no built-in quality profiles: %s", - languagesWithoutBuiltInQProfiles.stream().collect(Collectors.joining())); - } - - private Map<String, Map<String, BuiltInQualityProfile>> validateAndClean(BuiltInQualityProfilesDefinition.Context context) { - Map<String, Map<String, BuiltInQualityProfile>> profilesByLanguageAndName = context.profilesByLanguageAndName(); - profilesByLanguageAndName.entrySet() - .removeIf(entry -> { - String language = entry.getKey(); - if (languages.get(language) == null) { - LOGGER.info("Language {} is not installed, related quality profiles are ignored", language); - return true; - } - return false; - }); - - return profilesByLanguageAndName; - } - - private List<BuiltInQProfile> toFlatList(Map<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguage) { - if (rulesProfilesByLanguage.isEmpty()) { - return Collections.emptyList(); - } - - try (DbSession dbSession = dbClient.openSession(false)) { - Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = dbClient.ruleDao().selectAllDefinitions(dbSession) - .stream() - .collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getKey)); - Map<String, List<BuiltInQProfile.Builder>> buildersByLanguage = rulesProfilesByLanguage - .entrySet() - .stream() - .collect(MoreCollectors.uniqueIndex( - Map.Entry::getKey, - rulesProfilesByLanguageAndName -> toQualityProfileBuilders(rulesProfilesByLanguageAndName, rulesByRuleKey))); - return buildersByLanguage - .entrySet() - .stream() - .filter(BuiltInQProfileRepositoryImpl::ensureAtMostOneDeclaredDefault) - .map(entry -> toQualityProfiles(entry.getValue())) - .flatMap(Collection::stream) - .collect(MoreCollectors.toList()); - } - } - - /** - * Creates {@link BuiltInQProfile.Builder} for each unique quality profile name for a given language. - * Builders will have the following properties populated: - * <ul> - * <li>{@link BuiltInQProfile.Builder#language language}: key of the method's parameter</li> - * <li>{@link BuiltInQProfile.Builder#name name}: {@link RulesProfile#getName()}</li> - * <li>{@link BuiltInQProfile.Builder#declaredDefault declaredDefault}: {@code true} if at least one RulesProfile - * with a given name has {@link RulesProfile#getDefaultProfile()} is {@code true}</li> - * <li>{@link BuiltInQProfile.Builder#activeRules activeRules}: the concatenate of the active rules of all - * RulesProfile with a given name</li> - * </ul> - */ - private static List<BuiltInQProfile.Builder> toQualityProfileBuilders(Map.Entry<String, Map<String, BuiltInQualityProfile>> rulesProfilesByLanguageAndName, - Map<RuleKey, RuleDefinitionDto> rulesByRuleKey) { - String language = rulesProfilesByLanguageAndName.getKey(); - // use a LinkedHashMap to keep order of insertion of RulesProfiles - Map<String, BuiltInQProfile.Builder> qualityProfileBuildersByName = new LinkedHashMap<>(); - for (BuiltInQualityProfile builtInProfile : rulesProfilesByLanguageAndName.getValue().values()) { - qualityProfileBuildersByName.compute( - builtInProfile.name(), - (name, existingBuilder) -> updateOrCreateBuilder(language, existingBuilder, builtInProfile, rulesByRuleKey)); - } - return ImmutableList.copyOf(qualityProfileBuildersByName.values()); - } - - /** - * Fails if more than one {@link BuiltInQProfile.Builder#declaredDefault} is {@code true}, otherwise returns {@code true}. - */ - private static boolean ensureAtMostOneDeclaredDefault(Map.Entry<String, List<BuiltInQProfile.Builder>> entry) { - Set<String> declaredDefaultProfileNames = entry.getValue().stream() - .filter(BuiltInQProfile.Builder::isDeclaredDefault) - .map(BuiltInQProfile.Builder::getName) - .collect(MoreCollectors.toSet()); - checkState(declaredDefaultProfileNames.size() <= 1, "Several Quality profiles are flagged as default for the language %s: %s", entry.getKey(), declaredDefaultProfileNames); - return true; - } - - private static BuiltInQProfile.Builder updateOrCreateBuilder(String language, @Nullable BuiltInQProfile.Builder existingBuilder, BuiltInQualityProfile builtInProfile, - Map<RuleKey, RuleDefinitionDto> rulesByRuleKey) { - BuiltInQProfile.Builder builder = createOrReuseBuilder(existingBuilder, language, builtInProfile); - builder.setDeclaredDefault(builtInProfile.isDefault()); - builtInProfile.rules().forEach(builtInActiveRule -> { - RuleKey ruleKey = RuleKey.of(builtInActiveRule.repoKey(), builtInActiveRule.ruleKey()); - RuleDefinitionDto ruleDefinition = rulesByRuleKey.get(ruleKey); - checkState(ruleDefinition != null, "Rule with key '%s' not found", ruleKey); - builder.addRule(builtInActiveRule, ruleDefinition.getId()); - }); - return builder; - } - - private static BuiltInQProfile.Builder createOrReuseBuilder(@Nullable BuiltInQProfile.Builder existingBuilder, String language, BuiltInQualityProfile builtInProfile) { - if (existingBuilder == null) { - return new BuiltInQProfile.Builder() - .setLanguage(language) - .setName(builtInProfile.name()); - } - return existingBuilder; - } - - private static List<BuiltInQProfile> toQualityProfiles(List<BuiltInQProfile.Builder> builders) { - if (builders.stream().noneMatch(BuiltInQProfile.Builder::isDeclaredDefault)) { - Optional<BuiltInQProfile.Builder> sonarWayProfile = builders.stream().filter(builder -> builder.getName().equals(DEFAULT_PROFILE_NAME)).findFirst(); - if (sonarWayProfile.isPresent()) { - sonarWayProfile.get().setComputedDefault(true); - } else { - builders.iterator().next().setComputedDefault(true); - } - } - return builders.stream() - .map(BuiltInQProfile.Builder::build) - .collect(MoreCollectors.toList(builders.size())); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdate.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdate.java deleted file mode 100644 index 4992402a0d9..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdate.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.List; -import org.sonar.db.DbSession; -import org.sonar.db.qualityprofile.RulesProfileDto; - -public interface BuiltInQProfileUpdate { - /** - * Persist an existing built-in profile and associate it to all existing organizations. - * Db session is committed and Elasticsearch indices are updated. - */ - List<ActiveRuleChange> update(DbSession dbSession, BuiltInQProfile builtInQProfile, RulesProfileDto ruleProfile); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImpl.java deleted file mode 100644 index b711c69c15b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImpl.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.qualityprofile.ActiveRuleDto; -import org.sonar.db.qualityprofile.RulesProfileDto; -import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; - -import static org.sonar.core.util.stream.MoreCollectors.toSet; - -public class BuiltInQProfileUpdateImpl implements BuiltInQProfileUpdate { - - private final DbClient dbClient; - private final RuleActivator ruleActivator; - private final ActiveRuleIndexer activeRuleIndexer; - - public BuiltInQProfileUpdateImpl(DbClient dbClient, RuleActivator ruleActivator, ActiveRuleIndexer activeRuleIndexer) { - this.dbClient = dbClient; - this.ruleActivator = ruleActivator; - this.activeRuleIndexer = activeRuleIndexer; - } - - public List<ActiveRuleChange> update(DbSession dbSession, BuiltInQProfile builtInDefinition, RulesProfileDto initialRuleProfile) { - // Keep reference to all the activated rules before update - Set<Integer> deactivatedRuleIds = dbClient.activeRuleDao().selectByRuleProfile(dbSession, initialRuleProfile) - .stream() - .map(ActiveRuleDto::getRuleId) - .collect(MoreCollectors.toHashSet()); - - // all rules, including those which are removed from built-in profile - Set<Integer> ruleIds = Stream.concat( - deactivatedRuleIds.stream(), - builtInDefinition.getActiveRules().stream().map(BuiltInQProfile.ActiveRule::getRuleId)) - .collect(toSet()); - - Collection<RuleActivation> activations = new ArrayList<>(); - for (BuiltInQProfile.ActiveRule ar : builtInDefinition.getActiveRules()) { - RuleActivation activation = convert(ar); - activations.add(activation); - deactivatedRuleIds.remove(activation.getRuleId()); - } - - RuleActivationContext context = ruleActivator.createContextForBuiltInProfile(dbSession, initialRuleProfile, ruleIds); - List<ActiveRuleChange> changes = new ArrayList<>(); - for (RuleActivation activation : activations) { - changes.addAll(ruleActivator.activate(dbSession, activation, context)); - } - - // these rules are no longer part of the built-in profile - deactivatedRuleIds.forEach(ruleKey -> changes.addAll(ruleActivator.deactivate(dbSession, context, ruleKey, false))); - - activeRuleIndexer.commitAndIndex(dbSession, changes); - return changes; - } - - private static RuleActivation convert(BuiltInQProfile.ActiveRule ar) { - Map<String, String> params = ar.getBuiltIn().overriddenParams().stream() - .collect(MoreCollectors.uniqueIndex(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue)); - return RuleActivation.create(ar.getRuleId(), ar.getBuiltIn().overriddenSeverity(), params); - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQualityProfilesUpdateListener.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQualityProfilesUpdateListener.java deleted file mode 100644 index e37e84297db..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQualityProfilesUpdateListener.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import com.google.common.collect.Multimap; -import java.util.Collection; -import org.sonar.api.config.Configuration; -import org.sonar.api.resources.Language; -import org.sonar.api.resources.Languages; -import org.sonar.server.notification.NotificationManager; -import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.Profile; - -import static org.sonar.core.config.CorePropertyDefinitions.DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES; -import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED; -import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED; -import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.UPDATED; - -public class BuiltInQualityProfilesUpdateListener { - - private final NotificationManager notificationManager; - private final Languages languages; - private final Configuration config; - - public BuiltInQualityProfilesUpdateListener(NotificationManager notificationManager, Languages languages, Configuration config) { - this.notificationManager = notificationManager; - this.languages = languages; - this.config = config; - } - - void onChange(Multimap<QProfileName, ActiveRuleChange> changedProfiles, long startDate, long endDate) { - if (config.getBoolean(DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES).orElse(false)) { - return; - } - - BuiltInQPChangeNotificationBuilder builder = new BuiltInQPChangeNotificationBuilder(); - changedProfiles.keySet().stream() - .map(changedProfile -> { - String profileName = changedProfile.getName(); - Language language = languages.get(changedProfile.getLanguage()); - Collection<ActiveRuleChange> activeRuleChanges = changedProfiles.get(changedProfile); - int newRules = (int) activeRuleChanges.stream().map(ActiveRuleChange::getType).filter(ACTIVATED::equals).count(); - int updatedRules = (int) activeRuleChanges.stream().map(ActiveRuleChange::getType).filter(UPDATED::equals).count(); - int removedRules = (int) activeRuleChanges.stream().map(ActiveRuleChange::getType).filter(DEACTIVATED::equals).count(); - return Profile.newBuilder() - .setProfileName(profileName) - .setLanguageKey(language.getKey()) - .setLanguageName(language.getName()) - .setNewRules(newRules) - .setUpdatedRules(updatedRules) - .setRemovedRules(removedRules) - .setStartDate(startDate) - .setEndDate(endDate) - .build(); - }) - .forEach(builder::addProfile); - - notificationManager.scheduleForSending(builder.build()); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DescendantProfilesSupplier.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DescendantProfilesSupplier.java deleted file mode 100644 index ef073340e7a..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DescendantProfilesSupplier.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.Collection; -import org.sonar.db.qualityprofile.ActiveRuleDto; -import org.sonar.db.qualityprofile.ActiveRuleParamDto; -import org.sonar.db.qualityprofile.QProfileDto; - -@FunctionalInterface -public interface DescendantProfilesSupplier { - - Result get(Collection<QProfileDto> profiles, Collection<Integer> ruleIds); - - final class Result { - private final Collection<QProfileDto> profiles; - private final Collection<ActiveRuleDto> activeRules; - private final Collection<ActiveRuleParamDto> activeRuleParams; - - public Result(Collection<QProfileDto> profiles, Collection<ActiveRuleDto> activeRules, Collection<ActiveRuleParamDto> activeRuleParams) { - this.profiles = profiles; - this.activeRules = activeRules; - this.activeRuleParams = activeRuleParams; - } - - public Collection<QProfileDto> getProfiles() { - return profiles; - } - - public Collection<ActiveRuleDto> getActiveRules() { - return activeRules; - } - - public Collection<ActiveRuleParamDto> getActiveRuleParams() { - return activeRuleParams; - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileName.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileName.java deleted file mode 100644 index 8b134c00ece..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/QProfileName.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import javax.annotation.Nullable; - -public class QProfileName { - private final String lang; - private final String name; - - public QProfileName(String lang, String name) { - this.lang = lang; - this.name = name; - } - - public String getLanguage() { - return lang; - } - - public String getName() { - return name; - } - - public static QProfileName createFor(String lang, String name){ - return new QProfileName(lang, name); - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - QProfileName that = (QProfileName) o; - if (!lang.equals(that.lang)) { - return false; - } - return name.equals(that.name); - } - - @Override - public int hashCode() { - int result = lang.hashCode(); - result = 31 * result + name.hashCode(); - return result; - } - - @Override - public String toString() { - return String.format("%s/%s", lang, name); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RuleActivation.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RuleActivation.java deleted file mode 100644 index 106fd158b26..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RuleActivation.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import com.google.common.base.Strings; -import java.util.HashMap; -import java.util.Map; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import org.sonar.api.rule.Severity; - -/** - * The request for activation. - */ -@Immutable -public class RuleActivation { - - private final int ruleId; - private final boolean reset; - private final String severity; - private final Map<String, String> parameters = new HashMap<>(); - - private RuleActivation(int ruleId, boolean reset, @Nullable String severity, @Nullable Map<String, String> parameters) { - this.ruleId = ruleId; - this.reset = reset; - this.severity = severity; - if (severity != null && !Severity.ALL.contains(severity)) { - throw new IllegalArgumentException("Unknown severity: " + severity); - } - if (parameters != null) { - for (Map.Entry<String, String> entry : parameters.entrySet()) { - this.parameters.put(entry.getKey(), Strings.emptyToNull(entry.getValue())); - } - } - } - - public static RuleActivation createReset(int ruleId) { - return new RuleActivation(ruleId, true, null, null); - } - - public static RuleActivation create(int ruleId, @Nullable String severity, @Nullable Map<String, String> parameters) { - return new RuleActivation(ruleId, false, severity, parameters); - } - - public static RuleActivation create(int ruleId) { - return create(ruleId, null, null); - } - - /** - * Optional severity. Use the parent severity or default rule severity if null. - */ - @CheckForNull - public String getSeverity() { - return severity; - } - - public int getRuleId() { - return ruleId; - } - - @CheckForNull - public String getParameter(String key) { - return parameters.get(key); - } - - public boolean hasParameter(String key) { - return parameters.containsKey(key); - } - - public boolean isReset() { - return reset; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RuleActivationContext.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RuleActivationContext.java deleted file mode 100644 index 28a55cdb37f..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RuleActivationContext.java +++ /dev/null @@ -1,368 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ListMultimap; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import javax.annotation.CheckForNull; -import org.sonar.api.rule.RuleKey; -import org.sonar.db.qualityprofile.ActiveRuleDto; -import org.sonar.db.qualityprofile.ActiveRuleKey; -import org.sonar.db.qualityprofile.ActiveRuleParamDto; -import org.sonar.db.qualityprofile.QProfileDto; -import org.sonar.db.qualityprofile.RulesProfileDto; -import org.sonar.db.rule.RuleDefinitionDto; -import org.sonar.db.rule.RuleParamDto; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; -import static java.util.Objects.requireNonNull; -import static org.sonar.core.util.stream.MoreCollectors.index; -import static org.sonar.core.util.stream.MoreCollectors.toArrayList; -import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; -import static org.sonar.server.exceptions.BadRequestException.checkRequest; - -/** - * Cache of the data required to activate/deactivate - * multiple rules on a Quality profile, including - * the rule definitions, the rule parameters, the tree - * of profiles hierarchy and its related active rules. - */ -class RuleActivationContext { - - private final long date; - - // The profile that is initially targeted by the operation - private final RulesProfileDto baseRulesProfile; - - private final Map<String, QProfileDto> profilesByUuid = new HashMap<>(); - private final ListMultimap<String, QProfileDto> profilesByParentUuid = ArrayListMultimap.create(); - - // The rules/active rules involved in the group of activations/de-activations - private final Map<Integer, RuleWrapper> rulesById = new HashMap<>(); - private final Map<ActiveRuleKey, ActiveRuleWrapper> activeRulesByKey = new HashMap<>(); - - // Cursors used to move in the rules and in the tree of profiles. - - private RulesProfileDto currentRulesProfile; - // Cardinality is zero-to-many when cursor is on a built-in rules profile, - // otherwise it's always one, and only one (cursor on descendants or on non-built-in base profile). - private Collection<QProfileDto> currentProfiles; - private RuleWrapper currentRule; - private ActiveRuleWrapper currentActiveRule; - private ActiveRuleWrapper currentParentActiveRule; - - private boolean descendantsLoaded = false; - private final DescendantProfilesSupplier descendantProfilesSupplier; - - private RuleActivationContext(Builder builder) { - this.date = builder.date; - this.descendantProfilesSupplier = builder.descendantProfilesSupplier; - - ListMultimap<Integer, RuleParamDto> paramsByRuleId = builder.ruleParams.stream().collect(index(RuleParamDto::getRuleId)); - for (RuleDefinitionDto rule : builder.rules) { - RuleWrapper wrapper = new RuleWrapper(rule, paramsByRuleId.get(rule.getId())); - rulesById.put(rule.getId(), wrapper); - } - - this.baseRulesProfile = builder.baseRulesProfile; - register(builder.profiles); - register(builder.activeRules, builder.activeRuleParams); - } - - private void register(Collection<QProfileDto> profiles) { - for (QProfileDto profile : profiles) { - profilesByUuid.put(profile.getKee(), profile); - if (profile.getParentKee() != null) { - profilesByParentUuid.put(profile.getParentKee(), profile); - } - } - } - - private void register(Collection<ActiveRuleDto> activeRules, Collection<ActiveRuleParamDto> activeRuleParams) { - ListMultimap<Integer, ActiveRuleParamDto> paramsByActiveRuleId = activeRuleParams.stream().collect(index(ActiveRuleParamDto::getActiveRuleId)); - for (ActiveRuleDto activeRule : activeRules) { - ActiveRuleWrapper wrapper = new ActiveRuleWrapper(activeRule, paramsByActiveRuleId.get(activeRule.getId())); - this.activeRulesByKey.put(activeRule.getKey(), wrapper); - } - } - - long getDate() { - return date; - } - - /** - * The rule currently selected. - */ - RuleWrapper getRule() { - checkState(currentRule != null, "Rule has not been set yet"); - return currentRule; - } - - @CheckForNull - String getRequestedParamValue(RuleActivation request, String key) { - if (currentRule.rule.isCustomRule()) { - return null; - } - return request.getParameter(key); - } - - boolean hasRequestedParamValue(RuleActivation request, String key) { - return request.hasParameter(key); - } - - /** - * The rules profile being selected. - */ - RulesProfileDto getRulesProfile() { - checkState(currentRulesProfile != null, "Rule profile has not been set yet"); - return currentRulesProfile; - } - - /** - * The active rule related to the selected profile and rule. - * @return null if the selected rule is not activated on the selected profile. - * @see #getRulesProfile() - * @see #getRule() - */ - @CheckForNull - ActiveRuleWrapper getActiveRule() { - return currentActiveRule; - } - - /** - * The active rule related to the rule and the parent of the selected profile. - * @return null if the selected rule is not activated on the parent profile. - * @see #getRule() - */ - @CheckForNull - ActiveRuleWrapper getParentActiveRule() { - return currentParentActiveRule; - } - - /** - * Whether the profile cursor is on the base profile or not. - */ - boolean isCascading() { - return currentRulesProfile != null && !currentRulesProfile.getKee().equals(baseRulesProfile.getKee()); - } - - /** - * The profiles being selected. Can be zero or many if {@link #getRulesProfile()} is built-in. - * Else the collection always contains a single profile. - */ - Collection<QProfileDto> getProfiles() { - checkState(currentProfiles != null, "Profiles have not been set yet"); - return currentProfiles; - } - - /** - * The children of {@link #getProfiles()} - */ - Collection<QProfileDto> getChildProfiles() { - loadDescendants(); - return getProfiles().stream() - .flatMap(p -> profilesByParentUuid.get(p.getKee()).stream()) - .collect(Collectors.toList()); - } - - private void loadDescendants() { - if (descendantsLoaded) { - return; - } - Collection<QProfileDto> baseProfiles = profilesByUuid.values().stream() - .filter(p -> p.getRulesProfileUuid().equals(baseRulesProfile.getKee())) - .collect(toArrayList(profilesByUuid.size())); - DescendantProfilesSupplier.Result result = descendantProfilesSupplier.get(baseProfiles, rulesById.keySet()); - register(result.getProfiles()); - register(result.getActiveRules(), result.getActiveRuleParams()); - descendantsLoaded = true; - } - - /** - * Move the cursor to the given rule and back to the base profile. - */ - public void reset(int ruleId) { - doSwitch(this.baseRulesProfile, ruleId); - } - - /** - * Moves cursor to a child profile - */ - void selectChild(QProfileDto to) { - checkState(!to.isBuiltIn()); - QProfileDto qp = requireNonNull(this.profilesByUuid.get(to.getKee()), () -> "No profile with uuid " + to.getKee()); - - RulesProfileDto ruleProfile = RulesProfileDto.from(qp); - doSwitch(ruleProfile, getRule().get().getId()); - } - - private void doSwitch(RulesProfileDto ruleProfile, int ruleId) { - this.currentRule = rulesById.get(ruleId); - checkRequest(this.currentRule != null, "Rule with ID %s not found", ruleId); - RuleKey ruleKey = currentRule.get().getKey(); - - checkRequest(ruleProfile.getLanguage().equals(currentRule.get().getLanguage()), - "%s rule %s cannot be activated on %s profile %s", currentRule.get().getLanguage(), ruleKey, ruleProfile.getLanguage(), ruleProfile.getName()); - this.currentRulesProfile = ruleProfile; - this.currentProfiles = profilesByUuid.values().stream() - .filter(p -> p.getRulesProfileUuid().equals(ruleProfile.getKee())) - .collect(Collectors.toList()); - this.currentActiveRule = this.activeRulesByKey.get(ActiveRuleKey.of(ruleProfile, ruleKey)); - this.currentParentActiveRule = this.currentProfiles.stream() - .map(QProfileDto::getParentKee) - .filter(Objects::nonNull) - .map(profilesByUuid::get) - .filter(Objects::nonNull) - .findFirst() - .map(profile -> activeRulesByKey.get(ActiveRuleKey.of(profile, ruleKey))) - .orElse(null); - } - - static final class Builder { - private long date = System.currentTimeMillis(); - private RulesProfileDto baseRulesProfile; - private Collection<RuleDefinitionDto> rules; - private Collection<RuleParamDto> ruleParams; - private Collection<QProfileDto> profiles; - private Collection<ActiveRuleDto> activeRules; - private Collection<ActiveRuleParamDto> activeRuleParams; - private DescendantProfilesSupplier descendantProfilesSupplier; - - Builder setDate(long l) { - this.date = l; - return this; - } - - Builder setBaseProfile(RulesProfileDto p) { - this.baseRulesProfile = p; - return this; - } - - Builder setRules(Collection<RuleDefinitionDto> rules) { - this.rules = rules; - return this; - } - - Builder setRuleParams(Collection<RuleParamDto> ruleParams) { - this.ruleParams = ruleParams; - return this; - } - - /** - * All the profiles involved in the activation workflow, including the - * parent profile, even if it's not updated. - */ - Builder setProfiles(Collection<QProfileDto> profiles) { - this.profiles = profiles; - return this; - } - - Builder setActiveRules(Collection<ActiveRuleDto> activeRules) { - this.activeRules = activeRules; - return this; - } - - Builder setActiveRuleParams(Collection<ActiveRuleParamDto> activeRuleParams) { - this.activeRuleParams = activeRuleParams; - return this; - } - - Builder setDescendantProfilesSupplier(DescendantProfilesSupplier d) { - this.descendantProfilesSupplier = d; - return this; - } - - RuleActivationContext build() { - checkArgument(date > 0, "date is not set"); - requireNonNull(baseRulesProfile, "baseRulesProfile is null"); - requireNonNull(rules, "rules is null"); - requireNonNull(ruleParams, "ruleParams is null"); - requireNonNull(profiles, "profiles is null"); - requireNonNull(activeRules, "activeRules is null"); - requireNonNull(activeRuleParams, "activeRuleParams is null"); - requireNonNull(descendantProfilesSupplier, "descendantProfilesSupplier is null"); - return new RuleActivationContext(this); - } - } - - static final class RuleWrapper { - private final RuleDefinitionDto rule; - private final Map<String, RuleParamDto> paramsByKey; - - private RuleWrapper(RuleDefinitionDto rule, Collection<RuleParamDto> params) { - this.rule = rule; - this.paramsByKey = params.stream().collect(uniqueIndex(RuleParamDto::getName)); - } - - RuleDefinitionDto get() { - return rule; - } - - Collection<RuleParamDto> getParams() { - return paramsByKey.values(); - } - - @CheckForNull - RuleParamDto getParam(String key) { - return paramsByKey.get(key); - } - - @CheckForNull - String getParamDefaultValue(String key) { - RuleParamDto param = getParam(key); - return param != null ? param.getDefaultValue() : null; - } - } - - static final class ActiveRuleWrapper { - private final ActiveRuleDto activeRule; - private final Map<String, ActiveRuleParamDto> paramsByKey; - - private ActiveRuleWrapper(ActiveRuleDto activeRule, Collection<ActiveRuleParamDto> params) { - this.activeRule = activeRule; - this.paramsByKey = params.stream().collect(uniqueIndex(ActiveRuleParamDto::getKey)); - } - - ActiveRuleDto get() { - return activeRule; - } - - Collection<ActiveRuleParamDto> getParams() { - return paramsByKey.values(); - } - - @CheckForNull - ActiveRuleParamDto getParam(String key) { - return paramsByKey.get(key); - } - - @CheckForNull - String getParamValue(String key) { - ActiveRuleParamDto param = paramsByKey.get(key); - return param != null ? param.getValue() : null; - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RuleActivator.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RuleActivator.java deleted file mode 100644 index 2b5a2f8e19f..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RuleActivator.java +++ /dev/null @@ -1,479 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import com.google.common.base.Splitter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.rule.RuleStatus; -import org.sonar.api.server.ServerSide; -import org.sonar.api.server.rule.RuleParamType; -import org.sonar.api.utils.System2; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.qualityprofile.ActiveRuleDao; -import org.sonar.db.qualityprofile.ActiveRuleDto; -import org.sonar.db.qualityprofile.ActiveRuleKey; -import org.sonar.db.qualityprofile.ActiveRuleParamDto; -import org.sonar.db.qualityprofile.OrgQProfileDto; -import org.sonar.db.qualityprofile.QProfileDto; -import org.sonar.db.qualityprofile.RulesProfileDto; -import org.sonar.db.rule.RuleDefinitionDto; -import org.sonar.db.rule.RuleParamDto; -import org.sonar.server.qualityprofile.RuleActivationContext.ActiveRuleWrapper; -import org.sonar.server.qualityprofile.RuleActivationContext.RuleWrapper; -import org.sonar.server.user.UserSession; -import org.sonar.server.util.TypeValidations; - -import static com.google.common.base.Preconditions.checkArgument; -import static org.sonar.server.exceptions.BadRequestException.checkRequest; - -/** - * Activation and deactivation of rules in Quality profiles - */ -@ServerSide -public class RuleActivator { - - private final System2 system2; - private final DbClient db; - private final TypeValidations typeValidations; - private final UserSession userSession; - - public RuleActivator(System2 system2, DbClient db, TypeValidations typeValidations, UserSession userSession) { - this.system2 = system2; - this.db = db; - this.typeValidations = typeValidations; - this.userSession = userSession; - } - - public List<ActiveRuleChange> activate(DbSession dbSession, RuleActivation activation, RuleActivationContext context) { - context.reset(activation.getRuleId()); - return doActivate(dbSession, activation, context); - } - - private List<ActiveRuleChange> doActivate(DbSession dbSession, RuleActivation activation, RuleActivationContext context) { - RuleDefinitionDto rule = context.getRule().get(); - checkRequest(RuleStatus.REMOVED != rule.getStatus(), "Rule was removed: %s", rule.getKey()); - checkRequest(!rule.isTemplate(), "Rule template can't be activated on a Quality profile: %s", rule.getKey()); - - List<ActiveRuleChange> changes = new ArrayList<>(); - ActiveRuleChange change; - boolean stopCascading = false; - - ActiveRuleWrapper activeRule = context.getActiveRule(); - ActiveRuleKey activeRuleKey = ActiveRuleKey.of(context.getRulesProfile(), rule.getKey()); - if (activeRule == null) { - if (activation.isReset()) { - // ignore reset when rule is not activated - return changes; - } - // new activation - change = new ActiveRuleChange(ActiveRuleChange.Type.ACTIVATED, activeRuleKey, rule); - applySeverityAndParamToChange(activation, context, change); - if (context.isCascading() || isSameAsParent(change, context)) { - change.setInheritance(ActiveRuleInheritance.INHERITED); - } - } else { - // already activated - if (context.isCascading() && activeRule.get().doesOverride()) { - // propagating to descendants, but child profile already overrides rule -> stop propagation - return changes; - } - change = new ActiveRuleChange(ActiveRuleChange.Type.UPDATED, activeRuleKey, rule); - if (context.isCascading() && activeRule.get().getInheritance() == null) { - // activate on child, then on parent -> mark child as overriding parent - change.setInheritance(ActiveRuleInheritance.OVERRIDES); - change.setSeverity(activeRule.get().getSeverityString()); - for (ActiveRuleParamDto activeParam : activeRule.getParams()) { - change.setParameter(activeParam.getKey(), activeParam.getValue()); - } - stopCascading = true; - } else { - applySeverityAndParamToChange(activation, context, change); - if (!context.isCascading() && context.getParentActiveRule() != null) { - // override rule which is already declared on parents - change.setInheritance(isSameAsParent(change, context) ? ActiveRuleInheritance.INHERITED : ActiveRuleInheritance.OVERRIDES); - } - } - if (isSame(change, activeRule)) { - change = null; - stopCascading = true; - } - } - - if (change != null) { - changes.add(change); - persist(change, context, dbSession); - } - - if (!changes.isEmpty()) { - updateProfileDates(dbSession, context); - } - - if (!stopCascading) { - changes.addAll(propagateActivationToDescendants(dbSession, activation, context)); - } - - return changes; - } - - private void updateProfileDates(DbSession dbSession, RuleActivationContext context) { - RulesProfileDto ruleProfile = context.getRulesProfile(); - ruleProfile.setRulesUpdatedAtAsDate(new Date(context.getDate())); - db.qualityProfileDao().update(dbSession, ruleProfile); - - if (userSession.isLoggedIn()) { - context.getProfiles().forEach(p -> db.qualityProfileDao().update(dbSession, OrgQProfileDto.from(p).setUserUpdatedAt(context.getDate()))); - } - } - - /** - * Update severity and params - */ - private void applySeverityAndParamToChange(RuleActivation request, RuleActivationContext context, ActiveRuleChange change) { - RuleWrapper rule = context.getRule(); - ActiveRuleWrapper activeRule = context.getActiveRule(); - ActiveRuleWrapper parentActiveRule = context.getParentActiveRule(); - - // First apply severity - String severity; - if (request.isReset()) { - // load severity from parent profile, else from default values - severity = firstNonNull( - parentActiveRule != null ? parentActiveRule.get().getSeverityString() : null, - rule.get().getSeverityString()); - } else if (context.getRulesProfile().isBuiltIn()) { - // for builtin quality profiles, the severity from profile, when null use the default severity of the rule - severity = firstNonNull(request.getSeverity(), rule.get().getSeverityString()); - } else { - // load severity from request, else keep existing one (if already activated), else from parent, else from default - severity = firstNonNull( - request.getSeverity(), - activeRule == null ? null : activeRule.get().getSeverityString(), - parentActiveRule != null ? parentActiveRule.get().getSeverityString() : null, - rule.get().getSeverityString()); - } - change.setSeverity(severity); - - // Apply param values - for (RuleParamDto ruleParamDto : rule.getParams()) { - String paramKey = ruleParamDto.getName(); - String paramValue; - if (request.isReset()) { - // load params from parent profile, else from default values - paramValue = firstNonNull( - parentActiveRule != null ? parentActiveRule.getParamValue(paramKey) : null, - rule.getParamDefaultValue(paramKey)); - } else if (context.getRulesProfile().isBuiltIn()) { - // use the value defined in the profile definition, else the rule default value - paramValue = firstNonNull( - context.getRequestedParamValue(request, paramKey), - rule.getParamDefaultValue(paramKey)); - } else { - String parentValue = parentActiveRule != null ? parentActiveRule.getParamValue(paramKey) : null; - String activeRuleValue = activeRule == null ? null : activeRule.getParamValue(paramKey); - paramValue = context.hasRequestedParamValue(request, paramKey) ? - // If the request contains the parameter then we're using either value from request, or parent value, or default value - firstNonNull( - context.getRequestedParamValue(request, paramKey), - parentValue, - rule.getParamDefaultValue(paramKey)) - // If the request doesn't contain the parameter, then we're using either value in DB, or parent value, or default value - : firstNonNull( - activeRuleValue, - parentValue, - rule.getParamDefaultValue(paramKey)); - } - - change.setParameter(paramKey, validateParam(ruleParamDto, paramValue)); - } - } - - private List<ActiveRuleChange> propagateActivationToDescendants(DbSession dbSession, RuleActivation activation, RuleActivationContext context) { - List<ActiveRuleChange> changes = new ArrayList<>(); - - // get all inherited profiles - context.getChildProfiles().forEach(child -> { - context.selectChild(child); - changes.addAll(doActivate(dbSession, activation, context)); - }); - return changes; - } - - private void persist(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) { - ActiveRuleDto activeRule = null; - if (change.getType() == ActiveRuleChange.Type.ACTIVATED) { - activeRule = doInsert(change, context, dbSession); - } else if (change.getType() == ActiveRuleChange.Type.DEACTIVATED) { - ActiveRuleDao dao = db.activeRuleDao(); - activeRule = dao.delete(dbSession, change.getKey()).orElse(null); - - } else if (change.getType() == ActiveRuleChange.Type.UPDATED) { - activeRule = doUpdate(change, context, dbSession); - } - change.setActiveRule(activeRule); - db.qProfileChangeDao().insert(dbSession, change.toDto(userSession.getUuid())); - } - - private ActiveRuleDto doInsert(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) { - ActiveRuleDao dao = db.activeRuleDao(); - RuleWrapper rule = context.getRule(); - - ActiveRuleDto activeRule = new ActiveRuleDto(); - activeRule.setProfileId(context.getRulesProfile().getId()); - activeRule.setRuleId(rule.get().getId()); - activeRule.setKey(ActiveRuleKey.of(context.getRulesProfile(), rule.get().getKey())); - String severity = change.getSeverity(); - if (severity != null) { - activeRule.setSeverity(severity); - } - ActiveRuleInheritance inheritance = change.getInheritance(); - if (inheritance != null) { - activeRule.setInheritance(inheritance.name()); - } - activeRule.setUpdatedAt(system2.now()); - activeRule.setCreatedAt(system2.now()); - dao.insert(dbSession, activeRule); - for (Map.Entry<String, String> param : change.getParameters().entrySet()) { - if (param.getValue() != null) { - ActiveRuleParamDto paramDto = ActiveRuleParamDto.createFor(rule.getParam(param.getKey())); - paramDto.setValue(param.getValue()); - dao.insertParam(dbSession, activeRule, paramDto); - } - } - return activeRule; - } - - private ActiveRuleDto doUpdate(ActiveRuleChange change, RuleActivationContext context, DbSession dbSession) { - ActiveRuleWrapper activeRule = context.getActiveRule(); - if (activeRule == null) { - return null; - } - ActiveRuleDao dao = db.activeRuleDao(); - String severity = change.getSeverity(); - if (severity != null) { - activeRule.get().setSeverity(severity); - } - ActiveRuleInheritance inheritance = change.getInheritance(); - if (inheritance != null) { - activeRule.get().setInheritance(inheritance.name()); - } - activeRule.get().setUpdatedAt(system2.now()); - dao.update(dbSession, activeRule.get()); - - for (Map.Entry<String, String> param : change.getParameters().entrySet()) { - ActiveRuleParamDto activeRuleParamDto = activeRule.getParam(param.getKey()); - if (activeRuleParamDto == null) { - // did not exist - if (param.getValue() != null) { - activeRuleParamDto = ActiveRuleParamDto.createFor(context.getRule().getParam(param.getKey())); - activeRuleParamDto.setValue(param.getValue()); - dao.insertParam(dbSession, activeRule.get(), activeRuleParamDto); - } - } else { - if (param.getValue() != null) { - activeRuleParamDto.setValue(param.getValue()); - dao.updateParam(dbSession, activeRuleParamDto); - } else { - dao.deleteParam(dbSession, activeRuleParamDto); - } - } - } - return activeRule.get(); - } - - public List<ActiveRuleChange> deactivate(DbSession dbSession, RuleActivationContext context, int ruleId, boolean force) { - context.reset(ruleId); - return doDeactivate(dbSession, context, force); - } - - private List<ActiveRuleChange> doDeactivate(DbSession dbSession, RuleActivationContext context, boolean force) { - List<ActiveRuleChange> changes = new ArrayList<>(); - ActiveRuleWrapper activeRule = context.getActiveRule(); - if (activeRule == null) { - return changes; - } - - ActiveRuleChange change; - checkRequest(force || context.isCascading() || activeRule.get().getInheritance() == null, "Cannot deactivate inherited rule '%s'", context.getRule().get().getKey()); - change = new ActiveRuleChange(ActiveRuleChange.Type.DEACTIVATED, activeRule.get(), context.getRule().get()); - changes.add(change); - persist(change, context, dbSession); - - // get all inherited profiles (they are not built-in by design) - context.getChildProfiles().forEach(child -> { - context.selectChild(child); - changes.addAll(doDeactivate(dbSession, context, force)); - }); - - if (!changes.isEmpty()) { - updateProfileDates(dbSession, context); - } - - return changes; - } - - @CheckForNull - private String validateParam(RuleParamDto ruleParam, @Nullable String value) { - if (value != null) { - RuleParamType ruleParamType = RuleParamType.parse(ruleParam.getType()); - if (ruleParamType.multiple()) { - List<String> values = Splitter.on(",").splitToList(value); - typeValidations.validate(values, ruleParamType.type(), ruleParamType.values()); - } else { - typeValidations.validate(value, ruleParamType.type(), ruleParamType.values()); - } - } - return value; - } - - public RuleActivationContext createContextForBuiltInProfile(DbSession dbSession, RulesProfileDto builtInProfile, Collection<Integer> ruleIds) { - checkArgument(builtInProfile.isBuiltIn(), "Rules profile with UUID %s is not built-in", builtInProfile.getKee()); - - RuleActivationContext.Builder builder = new RuleActivationContext.Builder(); - builder.setDescendantProfilesSupplier(createDescendantProfilesSupplier(dbSession)); - - // load rules - completeWithRules(dbSession, builder, ruleIds); - - // load org profiles. Their parents are null by nature. - List<QProfileDto> profiles = db.qualityProfileDao().selectQProfilesByRuleProfile(dbSession, builtInProfile); - builder.setProfiles(profiles); - builder.setBaseProfile(builtInProfile); - - // load active rules - Collection<String> ruleProfileUuids = Stream - .concat(Stream.of(builtInProfile.getKee()), profiles.stream().map(QProfileDto::getRulesProfileUuid)) - .collect(MoreCollectors.toHashSet(profiles.size() + 1)); - completeWithActiveRules(dbSession, builder, ruleIds, ruleProfileUuids); - return builder.build(); - } - - public RuleActivationContext createContextForUserProfile(DbSession dbSession, QProfileDto profile, Collection<Integer> ruleIds) { - checkArgument(!profile.isBuiltIn(), "Profile with UUID %s is built-in", profile.getKee()); - RuleActivationContext.Builder builder = new RuleActivationContext.Builder(); - builder.setDescendantProfilesSupplier(createDescendantProfilesSupplier(dbSession)); - - // load rules - completeWithRules(dbSession, builder, ruleIds); - - // load profiles - List<QProfileDto> profiles = new ArrayList<>(); - profiles.add(profile); - if (profile.getParentKee() != null) { - profiles.add(db.qualityProfileDao().selectByUuid(dbSession, profile.getParentKee())); - } - builder.setProfiles(profiles); - builder.setBaseProfile(RulesProfileDto.from(profile)); - - // load active rules - Collection<String> ruleProfileUuids = profiles.stream() - .map(QProfileDto::getRulesProfileUuid) - .collect(MoreCollectors.toHashSet(profiles.size())); - completeWithActiveRules(dbSession, builder, ruleIds, ruleProfileUuids); - - return builder.build(); - } - - DescendantProfilesSupplier createDescendantProfilesSupplier(DbSession dbSession) { - return (parents, ruleIds) -> { - Collection<QProfileDto> profiles = db.qualityProfileDao().selectDescendants(dbSession, parents); - Set<String> ruleProfileUuids = profiles.stream() - .map(QProfileDto::getRulesProfileUuid) - .collect(MoreCollectors.toHashSet()); - Collection<ActiveRuleDto> activeRules = db.activeRuleDao().selectByRulesAndRuleProfileUuids(dbSession, ruleIds, ruleProfileUuids); - List<Integer> activeRuleIds = activeRules.stream().map(ActiveRuleDto::getId).collect(MoreCollectors.toArrayList(activeRules.size())); - List<ActiveRuleParamDto> activeRuleParams = db.activeRuleDao().selectParamsByActiveRuleIds(dbSession, activeRuleIds); - return new DescendantProfilesSupplier.Result(profiles, activeRules, activeRuleParams); - }; - } - - private void completeWithRules(DbSession dbSession, RuleActivationContext.Builder builder, Collection<Integer> ruleIds) { - List<RuleDefinitionDto> rules = db.ruleDao().selectDefinitionByIds(dbSession, ruleIds); - builder.setRules(rules); - builder.setRuleParams(db.ruleDao().selectRuleParamsByRuleIds(dbSession, ruleIds)); - } - - private void completeWithActiveRules(DbSession dbSession, RuleActivationContext.Builder builder, Collection<Integer> ruleIds, Collection<String> ruleProfileUuids) { - Collection<ActiveRuleDto> activeRules = db.activeRuleDao().selectByRulesAndRuleProfileUuids(dbSession, ruleIds, ruleProfileUuids); - builder.setActiveRules(activeRules); - List<Integer> activeRuleIds = activeRules.stream().map(ActiveRuleDto::getId).collect(MoreCollectors.toArrayList(activeRules.size())); - builder.setActiveRuleParams(db.activeRuleDao().selectParamsByActiveRuleIds(dbSession, activeRuleIds)); - } - - private static boolean isSame(ActiveRuleChange change, ActiveRuleWrapper activeRule) { - ActiveRuleInheritance inheritance = change.getInheritance(); - if (inheritance != null && !inheritance.name().equals(activeRule.get().getInheritance())) { - return false; - } - String severity = change.getSeverity(); - if (severity != null && !severity.equals(activeRule.get().getSeverityString())) { - return false; - } - for (Map.Entry<String, String> changeParam : change.getParameters().entrySet()) { - String activeParamValue = activeRule.getParamValue(changeParam.getKey()); - if (changeParam.getValue() == null && activeParamValue != null) { - return false; - } - if (changeParam.getValue() != null && (activeParamValue == null || !StringUtils.equals(changeParam.getValue(), activeParamValue))) { - return false; - } - } - return true; - } - - /** - * True if trying to override an inherited rule but with exactly the same values - */ - private static boolean isSameAsParent(ActiveRuleChange change, RuleActivationContext context) { - ActiveRuleWrapper parentActiveRule = context.getParentActiveRule(); - if (parentActiveRule == null) { - return false; - } - if (!StringUtils.equals(change.getSeverity(), parentActiveRule.get().getSeverityString())) { - return false; - } - for (Map.Entry<String, String> entry : change.getParameters().entrySet()) { - if (entry.getValue() != null && !entry.getValue().equals(parentActiveRule.getParamValue(entry.getKey()))) { - return false; - } - } - return true; - } - - @CheckForNull - private static String firstNonNull(String... strings) { - for (String s : strings) { - if (s != null) { - return s; - } - } - return null; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/AbstractUserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/AbstractUserSession.java deleted file mode 100644 index bba45afcf16..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/AbstractUserSession.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import com.google.common.collect.ImmutableSet; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.sonar.api.web.UserRole; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.db.user.UserDto; -import org.sonar.server.exceptions.ForbiddenException; -import org.sonar.server.exceptions.UnauthorizedException; - -import static java.lang.String.format; -import static org.apache.commons.lang.StringUtils.defaultString; -import static org.sonar.server.user.UserSession.IdentityProvider.SONARQUBE; - -public abstract class AbstractUserSession implements UserSession { - private static final Set<String> PUBLIC_PERMISSIONS = ImmutableSet.of(UserRole.USER, UserRole.CODEVIEWER); - private static final String INSUFFICIENT_PRIVILEGES_MESSAGE = "Insufficient privileges"; - private static final String AUTHENTICATION_IS_REQUIRED_MESSAGE = "Authentication is required"; - - protected static Identity computeIdentity(UserDto userDto) { - IdentityProvider identityProvider = IdentityProvider.getFromKey(userDto.getExternalIdentityProvider()); - ExternalIdentity externalIdentity = identityProvider == SONARQUBE ? null : externalIdentityOf(userDto); - return new Identity(identityProvider, externalIdentity); - } - - private static ExternalIdentity externalIdentityOf(UserDto userDto) { - String externalId = userDto.getExternalId(); - String externalLogin = userDto.getExternalLogin(); - return new ExternalIdentity(externalId, externalLogin); - } - - protected static final class Identity { - private final IdentityProvider identityProvider; - private final ExternalIdentity externalIdentity; - - private Identity(IdentityProvider identityProvider, @Nullable ExternalIdentity externalIdentity) { - this.identityProvider = identityProvider; - this.externalIdentity = externalIdentity; - } - - public IdentityProvider getIdentityProvider() { - return identityProvider; - } - - @CheckForNull - public ExternalIdentity getExternalIdentity() { - return externalIdentity; - } - } - - @Override - public final boolean hasPermission(OrganizationPermission permission, OrganizationDto organization) { - return hasPermission(permission, organization.getUuid()); - } - - @Override - public final boolean hasPermission(OrganizationPermission permission, String organizationUuid) { - return isRoot() || hasPermissionImpl(permission, organizationUuid); - } - - protected abstract boolean hasPermissionImpl(OrganizationPermission permission, String organizationUuid); - - @Override - public final boolean hasComponentPermission(String permission, ComponentDto component) { - if (isRoot()) { - return true; - } - String projectUuid = defaultString(component.getMainBranchProjectUuid(), component.projectUuid()); - return hasProjectUuidPermission(permission, projectUuid); - } - - @Override - public final boolean hasComponentUuidPermission(String permission, String componentUuid) { - if (isRoot()) { - return true; - } - Optional<String> projectUuid = componentUuidToProjectUuid(componentUuid); - return projectUuid - .map(s -> hasProjectUuidPermission(permission, s)) - .orElse(false); - } - - protected abstract Optional<String> componentUuidToProjectUuid(String componentUuid); - - protected abstract boolean hasProjectUuidPermission(String permission, String projectUuid); - - @Override - public final boolean hasMembership(OrganizationDto organizationDto) { - return isRoot() || hasMembershipImpl(organizationDto); - } - - protected abstract boolean hasMembershipImpl(OrganizationDto organizationDto); - - @Override - public final List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components) { - if (isRoot()) { - return new ArrayList<>(components); - } - return doKeepAuthorizedComponents(permission, components); - } - - /** - * Naive implementation, to be overridden if needed - */ - protected List<ComponentDto> doKeepAuthorizedComponents(String permission, Collection<ComponentDto> components) { - boolean allowPublicComponent = PUBLIC_PERMISSIONS.contains(permission); - return components.stream() - .filter(c -> (allowPublicComponent && !c.isPrivate()) || hasComponentPermission(permission, c)) - .collect(MoreCollectors.toList()); - } - - @Override - public UserSession checkIsRoot() { - if (!isRoot()) { - throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE); - } - return this; - } - - @Override - public final UserSession checkLoggedIn() { - if (!isLoggedIn()) { - throw new UnauthorizedException(AUTHENTICATION_IS_REQUIRED_MESSAGE); - } - return this; - } - - @Override - public final UserSession checkPermission(OrganizationPermission permission, OrganizationDto organization) { - return checkPermission(permission, organization.getUuid()); - } - - @Override - public final UserSession checkPermission(OrganizationPermission permission, String organizationUuid) { - if (!hasPermission(permission, organizationUuid)) { - throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE); - } - return this; - } - - @Override - public final UserSession checkComponentPermission(String projectPermission, ComponentDto component) { - if (!hasComponentPermission(projectPermission, component)) { - throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE); - } - return this; - } - - @Override - public final UserSession checkComponentUuidPermission(String permission, String componentUuid) { - if (!hasComponentUuidPermission(permission, componentUuid)) { - throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE); - } - return this; - } - - public static ForbiddenException insufficientPrivilegesException() { - return new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE); - } - - @Override - public final UserSession checkIsSystemAdministrator() { - if (!isSystemAdministrator()) { - throw insufficientPrivilegesException(); - } - return this; - } - - @Override - public UserSession checkMembership(OrganizationDto organization) { - if (!hasMembership(organization)) { - throw new ForbiddenException(format("You're not member of organization '%s'", organization.getKey())); - } - return this; - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/CompatibilityRealm.java b/server/sonar-server/src/main/java/org/sonar/server/user/CompatibilityRealm.java deleted file mode 100644 index 2f2aa1df59e..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/CompatibilityRealm.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.sonar.api.security.LoginPasswordAuthenticator; -import org.sonar.api.security.SecurityRealm; - -/** - * Provides backward compatibility for {@link org.sonar.api.CoreProperties#CORE_AUTHENTICATOR_CLASS}. - * - * @since 2.14 - */ -class CompatibilityRealm extends SecurityRealm { - private final LoginPasswordAuthenticator authenticator; - - public CompatibilityRealm(LoginPasswordAuthenticator authenticator) { - this.authenticator = authenticator; - } - - @Override - public void init() { - authenticator.init(); - } - - @Override - public String getName() { - return "CompatibilityRealm[" + authenticator.getClass().getName() + "]"; - } - - @Override - public LoginPasswordAuthenticator getLoginPasswordAuthenticator() { - return authenticator; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/DoPrivileged.java b/server/sonar-server/src/main/java/org/sonar/server/user/DoPrivileged.java deleted file mode 100644 index 4f695b25f8d..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/DoPrivileged.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.db.user.GroupDto; - -/** - * Allow code to be executed with the highest privileges possible, as if executed by a {@link OrganizationPermission#ADMINISTER} account. - * @since 4.3 - */ -public final class DoPrivileged { - - private DoPrivileged() { - // Only static stuff - } - - /** - * Executes the task's <code>{@link Task#doPrivileged() doPrivileged}</code> method in a privileged environment. - * @param task - */ - public static void execute(Task task) { - try { - task.start(); - task.doPrivileged(); - } finally { - task.stop(); - } - } - - /** - * Define a task that will be executed using the highest privileges available. The privileged section is restricted - * to the execution of the {@link #doPrivileged()} method. - */ - public abstract static class Task { - private final ThreadLocalUserSession threadLocalUserSession; - private UserSession oldUserSession; - - protected Task(ThreadLocalUserSession threadLocalUserSession) { - this.threadLocalUserSession = threadLocalUserSession; - } - - /** - * Code placed in this method will be executed in a privileged environment. - */ - protected abstract void doPrivileged(); - - private static class PrivilegedUserSession extends AbstractUserSession { - @Override - public String getLogin() { - return null; - } - - @Override - public String getUuid() { - return null; - } - - @Override - public String getName() { - return null; - } - - @Override - public Integer getUserId() { - return null; - } - - @Override - public Collection<GroupDto> getGroups() { - return Collections.emptyList(); - } - - @Override - public boolean isLoggedIn() { - return false; - } - - @Override - public boolean isRoot() { - return true; - } - - @Override - public Optional<IdentityProvider> getIdentityProvider() { - return Optional.empty(); - } - - @Override - public Optional<ExternalIdentity> getExternalIdentity() { - return Optional.empty(); - } - - @Override - protected boolean hasPermissionImpl(OrganizationPermission permission, String organizationUuid) { - return true; - } - - @Override - protected Optional<String> componentUuidToProjectUuid(String componentUuid) { - // always root so unused - throw new UnsupportedOperationException(); - } - - @Override - protected boolean hasProjectUuidPermission(String permission, String projectUuid) { - return true; - } - - @Override - public boolean isSystemAdministrator() { - return true; - } - - @Override - public boolean hasMembershipImpl(OrganizationDto organizationDto) { - return true; - } - } - - private void start() { - oldUserSession = threadLocalUserSession.hasSession() ? threadLocalUserSession.get() : null; - threadLocalUserSession.set(new PrivilegedUserSession()); - } - - private void stop() { - threadLocalUserSession.unload(); - if (oldUserSession != null) { - threadLocalUserSession.set(oldUserSession); - } - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ExternalIdentity.java b/server/sonar-server/src/main/java/org/sonar/server/user/ExternalIdentity.java deleted file mode 100644 index 483f4590ca2..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ExternalIdentity.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import javax.annotation.Nullable; - -import static java.util.Objects.requireNonNull; - -public class ExternalIdentity { - - public static final String SQ_AUTHORITY = "sonarqube"; - - private String provider; - private String login; - private String id; - - public ExternalIdentity(String provider, String login, @Nullable String id) { - this.provider = requireNonNull(provider, "Identity provider cannot be null"); - this.login = requireNonNull(login, "Identity login cannot be null"); - this.id = id == null ? login : id; - } - - public String getProvider() { - return provider; - } - - public String getLogin() { - return login; - } - - public String getId() { - return id; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/NewUser.java b/server/sonar-server/src/main/java/org/sonar/server/user/NewUser.java deleted file mode 100644 index cd7b51116ee..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/NewUser.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import java.util.ArrayList; -import java.util.List; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - -import static com.google.common.base.Preconditions.checkState; - -public class NewUser { - - private String login; - private String password; - private String name; - private String email; - private List<String> scmAccounts; - private ExternalIdentity externalIdentity; - - private NewUser(Builder builder) { - this.login = builder.login; - this.password = builder.password; - this.name = builder.name; - this.email = builder.email; - this.scmAccounts = builder.scmAccounts; - this.externalIdentity = builder.externalIdentity; - } - - @CheckForNull - public String login() { - return login; - } - - public String name() { - return name; - } - - @CheckForNull - public String email() { - return email; - } - - public NewUser setEmail(@Nullable String email) { - this.email = email; - return this; - } - - public List<String> scmAccounts() { - return scmAccounts; - } - - public NewUser setScmAccounts(List<String> scmAccounts) { - this.scmAccounts = scmAccounts; - return this; - } - - @Nullable - public String password() { - return password; - } - - public NewUser setPassword(@Nullable String password) { - this.password = password; - return this; - } - - @Nullable - public ExternalIdentity externalIdentity() { - return externalIdentity; - } - - public NewUser setExternalIdentity(@Nullable ExternalIdentity externalIdentity) { - this.externalIdentity = externalIdentity; - return this; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private String login; - private String name; - private String email; - private List<String> scmAccounts = new ArrayList<>(); - private String password; - private ExternalIdentity externalIdentity; - - public Builder setLogin(@Nullable String login) { - this.login = login; - return this; - } - - public Builder setName(String name) { - this.name = name; - return this; - } - - public Builder setEmail(@Nullable String email) { - this.email = email; - return this; - } - - public Builder setScmAccounts(List<String> scmAccounts) { - this.scmAccounts = scmAccounts; - return this; - } - - public Builder setPassword(@Nullable String password) { - this.password = password; - return this; - } - - public Builder setExternalIdentity(@Nullable ExternalIdentity externalIdentity) { - this.externalIdentity = externalIdentity; - return this; - } - - public NewUser build() { - checkState(externalIdentity == null || password == null, "Password should not be set with an external identity"); - return new NewUser(this); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/NewUserNotifier.java b/server/sonar-server/src/main/java/org/sonar/server/user/NewUserNotifier.java deleted file mode 100644 index e5dc562bd70..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/NewUserNotifier.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.sonar.api.server.ServerSide; -import org.sonar.api.platform.NewUserHandler; -import org.sonar.api.utils.log.Loggers; - -/** - * @since 3.2 - */ -@ServerSide -public class NewUserNotifier { - - private NewUserHandler[] handlers; - - public NewUserNotifier(NewUserHandler[] handlers) { - this.handlers = handlers; - } - - public NewUserNotifier() { - this(new NewUserHandler[0]); - } - - public void onNewUser(NewUserHandler.Context context) { - Loggers.get(NewUserNotifier.class).debug("User created: " + context.getLogin() + ". Notifying " + NewUserHandler.class.getSimpleName() + " handlers..."); - for (NewUserHandler handler : handlers) { - handler.doOnNewUser(context); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/SecurityRealmFactory.java b/server/sonar-server/src/main/java/org/sonar/server/user/SecurityRealmFactory.java deleted file mode 100644 index f98f6ac58ac..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/SecurityRealmFactory.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import javax.annotation.Nullable; -import org.apache.commons.lang.StringUtils; -import org.picocontainer.Startable; -import org.sonar.api.CoreProperties; -import org.sonar.api.config.Configuration; -import org.sonar.api.security.LoginPasswordAuthenticator; -import org.sonar.api.security.SecurityRealm; -import org.sonar.api.server.ServerSide; -import org.sonar.api.utils.SonarException; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; - -import static org.sonar.process.ProcessProperties.Property.SONAR_AUTHENTICATOR_IGNORE_STARTUP_FAILURE; -import static org.sonar.process.ProcessProperties.Property.SONAR_SECURITY_REALM; - -/** - * @since 2.14 - */ -@ServerSide -public class SecurityRealmFactory implements Startable { - - private final boolean ignoreStartupFailure; - private final SecurityRealm realm; - - public SecurityRealmFactory(Configuration config, SecurityRealm[] realms, LoginPasswordAuthenticator[] authenticators) { - ignoreStartupFailure = config.getBoolean(SONAR_AUTHENTICATOR_IGNORE_STARTUP_FAILURE.getKey()).orElse(false); - String realmName = config.get(SONAR_SECURITY_REALM.getKey()).orElse(null); - String className = config.get(CoreProperties.CORE_AUTHENTICATOR_CLASS).orElse(null); - SecurityRealm selectedRealm = null; - if (!StringUtils.isEmpty(realmName)) { - selectedRealm = selectRealm(realms, realmName); - if (selectedRealm == null) { - throw new SonarException(String.format( - "Realm '%s' not found. Please check the property '%s' in conf/sonar.properties", realmName, SONAR_SECURITY_REALM.getKey())); - } - } - if (selectedRealm == null && !StringUtils.isEmpty(className)) { - LoginPasswordAuthenticator authenticator = selectAuthenticator(authenticators, className); - if (authenticator == null) { - throw new SonarException(String.format( - "Authenticator '%s' not found. Please check the property '%s' in conf/sonar.properties", className, CoreProperties.CORE_AUTHENTICATOR_CLASS)); - } - selectedRealm = new CompatibilityRealm(authenticator); - } - realm = selectedRealm; - } - - public SecurityRealmFactory(Configuration config, LoginPasswordAuthenticator[] authenticators) { - this(config, new SecurityRealm[0], authenticators); - } - - public SecurityRealmFactory(Configuration config, SecurityRealm[] realms) { - this(config, realms, new LoginPasswordAuthenticator[0]); - } - - public SecurityRealmFactory(Configuration config) { - this(config, new SecurityRealm[0], new LoginPasswordAuthenticator[0]); - } - - @Override - public void start() { - if (realm != null) { - Logger logger = Loggers.get("org.sonar.INFO"); - try { - logger.info("Security realm: " + realm.getName()); - realm.init(); - logger.info("Security realm started"); - } catch (RuntimeException e) { - if (ignoreStartupFailure) { - logger.error("IGNORED - Security realm fails to start: " + e.getMessage()); - } else { - throw new SonarException("Security realm fails to start: " + e.getMessage(), e); - } - } - } - } - - @Override - public void stop() { - // nothing - } - - @Nullable - public SecurityRealm getRealm() { - return realm; - } - - public boolean hasExternalAuthentication() { - return getRealm() != null; - } - - private static SecurityRealm selectRealm(SecurityRealm[] realms, String realmName) { - for (SecurityRealm realm : realms) { - if (StringUtils.equals(realmName, realm.getName())) { - return realm; - } - } - return null; - } - - private static LoginPasswordAuthenticator selectAuthenticator(LoginPasswordAuthenticator[] authenticators, String className) { - for (LoginPasswordAuthenticator lpa : authenticators) { - if (lpa.getClass().getName().equals(className)) { - return lpa; - } - } - return null; - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ServerUserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/ServerUserSession.java deleted file mode 100644 index 1485eca1891..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ServerUserSession.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import com.google.common.collect.ImmutableSet; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.organization.OrganizationMemberDto; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.OrganizationFlags; - -import static java.util.Objects.requireNonNull; -import static java.util.Optional.of; -import static java.util.Optional.ofNullable; -import static org.apache.commons.lang.StringUtils.defaultIfEmpty; -import static org.sonar.api.web.UserRole.PUBLIC_PERMISSIONS; - -/** - * Implementation of {@link UserSession} used in web server - */ -public class ServerUserSession extends AbstractUserSession { - @CheckForNull - private final UserDto userDto; - private final DbClient dbClient; - private final OrganizationFlags organizationFlags; - private final DefaultOrganizationProvider defaultOrganizationProvider; - private final Supplier<Collection<GroupDto>> groups = Suppliers.memoize(this::loadGroups); - private final Supplier<Boolean> isSystemAdministratorSupplier = Suppliers.memoize(this::loadIsSystemAdministrator); - private final Map<String, String> projectUuidByComponentUuid = new HashMap<>(); - private Map<String, Set<OrganizationPermission>> permissionsByOrganizationUuid; - private Map<String, Set<String>> permissionsByProjectUuid; - private Set<String> organizationMembership = new HashSet<>(); - - ServerUserSession(DbClient dbClient, OrganizationFlags organizationFlags, - DefaultOrganizationProvider defaultOrganizationProvider, @Nullable UserDto userDto) { - this.dbClient = dbClient; - this.organizationFlags = organizationFlags; - this.defaultOrganizationProvider = defaultOrganizationProvider; - this.userDto = userDto; - } - - private Collection<GroupDto> loadGroups() { - if (this.userDto == null) { - return Collections.emptyList(); - } - try (DbSession dbSession = dbClient.openSession(false)) { - return dbClient.groupDao().selectByUserLogin(dbSession, userDto.getLogin()); - } - } - - @Override - @CheckForNull - public String getLogin() { - return userDto == null ? null : userDto.getLogin(); - } - - @Override - @CheckForNull - public String getUuid() { - return userDto == null ? null : userDto.getUuid(); - } - - @Override - @CheckForNull - public String getName() { - return userDto == null ? null : userDto.getName(); - } - - @Override - @CheckForNull - public Integer getUserId() { - return userDto == null ? null : userDto.getId(); - } - - @Override - public Collection<GroupDto> getGroups() { - return groups.get(); - } - - @Override - public boolean isLoggedIn() { - return userDto != null; - } - - @Override - public boolean isRoot() { - return userDto != null && userDto.isRoot(); - } - - @Override - public Optional<IdentityProvider> getIdentityProvider() { - return ofNullable(userDto).map(d -> computeIdentity(d).getIdentityProvider()); - } - - @Override - public Optional<ExternalIdentity> getExternalIdentity() { - return ofNullable(userDto).map(d -> computeIdentity(d).getExternalIdentity()); - } - - @Override - protected boolean hasPermissionImpl(OrganizationPermission permission, String organizationUuid) { - if (permissionsByOrganizationUuid == null) { - permissionsByOrganizationUuid = new HashMap<>(); - } - Set<OrganizationPermission> permissions = permissionsByOrganizationUuid.computeIfAbsent(organizationUuid, this::loadOrganizationPermissions); - return permissions.contains(permission); - } - - private Set<OrganizationPermission> loadOrganizationPermissions(String organizationUuid) { - Set<String> permissionKeys; - try (DbSession dbSession = dbClient.openSession(false)) { - if (userDto != null && userDto.getId() != null) { - permissionKeys = dbClient.authorizationDao().selectOrganizationPermissions(dbSession, organizationUuid, userDto.getId()); - } else { - permissionKeys = dbClient.authorizationDao().selectOrganizationPermissionsOfAnonymous(dbSession, organizationUuid); - } - } - return permissionKeys.stream() - .map(OrganizationPermission::fromKey) - .collect(MoreCollectors.toSet(permissionKeys.size())); - } - - @Override - protected Optional<String> componentUuidToProjectUuid(String componentUuid) { - String projectUuid = projectUuidByComponentUuid.get(componentUuid); - if (projectUuid != null) { - return of(projectUuid); - } - try (DbSession dbSession = dbClient.openSession(false)) { - Optional<ComponentDto> component = dbClient.componentDao().selectByUuid(dbSession, componentUuid); - if (!component.isPresent()) { - return Optional.empty(); - } - // if component is part of a branch, then permissions must be - // checked on the project (represented by its main branch) - projectUuid = defaultIfEmpty(component.get().getMainBranchProjectUuid(), component.get().projectUuid()); - projectUuidByComponentUuid.put(componentUuid, projectUuid); - return of(projectUuid); - } - } - - @Override - protected boolean hasProjectUuidPermission(String permission, String projectUuid) { - if (permissionsByProjectUuid == null) { - permissionsByProjectUuid = new HashMap<>(); - } - Set<String> permissions = permissionsByProjectUuid.computeIfAbsent(projectUuid, this::loadProjectPermissions); - return permissions.contains(permission); - } - - private Set<String> loadProjectPermissions(String projectUuid) { - try (DbSession dbSession = dbClient.openSession(false)) { - Optional<ComponentDto> component = dbClient.componentDao().selectByUuid(dbSession, projectUuid); - if (!component.isPresent()) { - return Collections.emptySet(); - } - if (component.get().isPrivate()) { - return loadDbPermissions(dbSession, projectUuid); - } - ImmutableSet.Builder<String> builder = ImmutableSet.builder(); - builder.addAll(PUBLIC_PERMISSIONS); - builder.addAll(loadDbPermissions(dbSession, projectUuid)); - return builder.build(); - } - } - - private Set<String> loadDbPermissions(DbSession dbSession, String projectUuid) { - if (userDto != null && userDto.getId() != null) { - return dbClient.authorizationDao().selectProjectPermissions(dbSession, projectUuid, userDto.getId()); - } - return dbClient.authorizationDao().selectProjectPermissionsOfAnonymous(dbSession, projectUuid); - } - - @Override - protected List<ComponentDto> doKeepAuthorizedComponents(String permission, Collection<ComponentDto> components) { - try (DbSession dbSession = dbClient.openSession(false)) { - Set<String> projectUuids = components.stream() - .map(c -> defaultIfEmpty(c.getMainBranchProjectUuid(), c.projectUuid())) - .collect(MoreCollectors.toSet(components.size())); - Set<String> authorizedProjectUuids = dbClient.authorizationDao().keepAuthorizedProjectUuids(dbSession, projectUuids, getUserId(), permission); - - return components.stream() - .filter(c -> authorizedProjectUuids.contains(c.projectUuid()) || authorizedProjectUuids.contains(c.getMainBranchProjectUuid())) - .collect(MoreCollectors.toList(components.size())); - } - } - - @Override - public boolean isSystemAdministrator() { - return isSystemAdministratorSupplier.get(); - } - - private boolean loadIsSystemAdministrator() { - if (isRoot()) { - return true; - } - try (DbSession dbSession = dbClient.openSession(false)) { - if (!organizationFlags.isEnabled(dbSession)) { - String uuidOfDefaultOrg = defaultOrganizationProvider.get().getUuid(); - return hasPermission(OrganizationPermission.ADMINISTER, uuidOfDefaultOrg); - } - // organization feature is enabled -> requires to be root - return false; - } - } - - @Override - public boolean hasMembershipImpl(OrganizationDto organizationDto) { - return isMember(organizationDto.getUuid()); - } - - private boolean isMember(String organizationUuid) { - if (!isLoggedIn()) { - return false; - } - if (isRoot()) { - return true; - } - - if (organizationMembership.contains(organizationUuid)) { - return true; - } - try (DbSession dbSession = dbClient.openSession(false)) { - Optional<OrganizationMemberDto> organizationMemberDto = dbClient.organizationMemberDao().select(dbSession, organizationUuid, requireNonNull(getUserId())); - if (organizationMemberDto.isPresent()) { - organizationMembership.add(organizationUuid); - } - return organizationMembership.contains(organizationUuid); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscode.java b/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscode.java deleted file mode 100644 index 238f3088325..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscode.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.sonar.api.server.ws.Request; - -/** - * Passcode for accessing some web services, usually for connecting - * monitoring tools without using the credentials - * of a system administrator. - * - * Important - the web services accepting passcode must be listed in - * {@link org.sonar.server.authentication.UserSessionInitializer#URL_USING_PASSCODE}. - */ -public interface SystemPasscode { - - /** - * Whether the system passcode is provided by the HTTP request or not. - * Returns {@code false} if passcode is not configured. - */ - boolean isValid(Request request); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscodeImpl.java b/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscodeImpl.java deleted file mode 100644 index 1f68993e59b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscodeImpl.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import java.util.Optional; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.Startable; -import org.sonar.api.config.Configuration; -import org.sonar.api.server.ServerSide; -import org.sonar.api.server.ws.Request; -import org.sonar.api.utils.log.Loggers; - -@ServerSide -public class SystemPasscodeImpl implements SystemPasscode, Startable { - - public static final String PASSCODE_HTTP_HEADER = "X-Sonar-Passcode"; - public static final String PASSCODE_CONF_PROPERTY = "sonar.web.systemPasscode"; - - private final Configuration configuration; - private String configuredPasscode; - - public SystemPasscodeImpl(Configuration configuration) { - this.configuration = configuration; - } - - @Override - public boolean isValid(Request request) { - if (configuredPasscode == null) { - return false; - } - return request.header(PASSCODE_HTTP_HEADER) - .map(s -> configuredPasscode.equals(s)) - .orElse(false); - } - - @Override - public void start() { - Optional<String> passcodeOpt = configuration.get(PASSCODE_CONF_PROPERTY) - // if present, result is never empty string - .map(StringUtils::trimToNull); - - if (passcodeOpt.isPresent()) { - logState("enabled"); - configuredPasscode = passcodeOpt.get(); - } else { - logState("disabled"); - configuredPasscode = null; - } - } - - private void logState(String state) { - Loggers.get(getClass()).info("System authentication by passcode is {}", state); - } - - @Override - public void stop() { - // nothing to do - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java deleted file mode 100644 index 3e5712bc5fa..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import javax.annotation.CheckForNull; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.db.user.GroupDto; -import org.sonar.server.exceptions.UnauthorizedException; - -/** - * Part of the current HTTP session - */ -public class ThreadLocalUserSession implements UserSession { - - private static final ThreadLocal<UserSession> DELEGATE = new ThreadLocal<>(); - - public UserSession get() { - UserSession session = DELEGATE.get(); - if (session != null) { - return session; - } - throw new UnauthorizedException("User is not authenticated"); - } - - public void set(UserSession session) { - DELEGATE.set(session); - } - - public void unload() { - DELEGATE.remove(); - } - - public boolean hasSession() { - return DELEGATE.get() != null; - } - - @Override - @CheckForNull - public String getLogin() { - return get().getLogin(); - } - - @Override - @CheckForNull - public String getUuid() { - return get().getUuid(); - } - - @Override - @CheckForNull - public String getName() { - return get().getName(); - } - - @Override - @CheckForNull - public Integer getUserId() { - return get().getUserId(); - } - - @Override - public Collection<GroupDto> getGroups() { - return get().getGroups(); - } - - @Override - public Optional<IdentityProvider> getIdentityProvider() { - return get().getIdentityProvider(); - } - - @Override - public Optional<ExternalIdentity> getExternalIdentity() { - return get().getExternalIdentity(); - } - - @Override - public boolean isLoggedIn() { - return get().isLoggedIn(); - } - - @Override - public UserSession checkIsRoot() { - return get().checkIsRoot(); - } - - @Override - public boolean isRoot() { - return get().isRoot(); - } - - @Override - public UserSession checkLoggedIn() { - get().checkLoggedIn(); - return this; - } - - @Override - public boolean hasPermission(OrganizationPermission permission, String organizationUuid) { - return get().hasPermission(permission, organizationUuid); - } - - @Override - public UserSession checkPermission(OrganizationPermission permission, String organizationUuid) { - get().checkPermission(permission, organizationUuid); - return this; - } - - @Override - public UserSession checkComponentPermission(String projectPermission, ComponentDto component) { - get().checkComponentPermission(projectPermission, component); - return this; - } - - @Override - public UserSession checkComponentUuidPermission(String permission, String componentUuid) { - get().checkComponentUuidPermission(permission, componentUuid); - return this; - } - - @Override - public boolean isSystemAdministrator() { - return get().isSystemAdministrator(); - } - - @Override - public UserSession checkIsSystemAdministrator() { - get().checkIsSystemAdministrator(); - return this; - } - - @Override - public boolean hasComponentPermission(String permission, ComponentDto component) { - return get().hasComponentPermission(permission, component); - } - - @Override - public boolean hasComponentUuidPermission(String permission, String componentUuid) { - return get().hasComponentUuidPermission(permission, componentUuid); - } - - @Override - public UserSession checkPermission(OrganizationPermission permission, OrganizationDto organization) { - get().checkPermission(permission, organization); - return this; - } - - @Override - public boolean hasPermission(OrganizationPermission permission, OrganizationDto organization) { - return get().hasPermission(permission, organization); - } - - @Override - public List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components) { - return get().keepAuthorizedComponents(permission, components); - } - - @Override - public boolean hasMembership(OrganizationDto organizationDto) { - return get().hasMembership(organizationDto); - } - - @Override - public UserSession checkMembership(OrganizationDto organization) { - get().checkMembership(organization); - return this; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UpdateUser.java b/server/sonar-server/src/main/java/org/sonar/server/user/UpdateUser.java deleted file mode 100644 index 746867d8285..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/UpdateUser.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import java.util.List; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - -public class UpdateUser { - - private String login; - private String name; - private String email; - private List<String> scmAccounts; - private String password; - private ExternalIdentity externalIdentity; - - private boolean loginChanged; - private boolean nameChanged; - private boolean emailChanged; - private boolean scmAccountsChanged; - private boolean passwordChanged; - private boolean externalIdentityChanged; - - @CheckForNull - public String login() { - return login; - } - - public UpdateUser setLogin(@Nullable String login) { - this.login = login; - loginChanged = true; - return this; - } - - @CheckForNull - public String name() { - return name; - } - - public UpdateUser setName(@Nullable String name) { - this.name = name; - nameChanged = true; - return this; - } - - @CheckForNull - public String email() { - return email; - } - - public UpdateUser setEmail(@Nullable String email) { - this.email = email; - emailChanged = true; - return this; - } - - @CheckForNull - public List<String> scmAccounts() { - return scmAccounts; - } - - public UpdateUser setScmAccounts(@Nullable List<String> scmAccounts) { - this.scmAccounts = scmAccounts; - scmAccountsChanged = true; - return this; - } - - @CheckForNull - public String password() { - return password; - } - - public UpdateUser setPassword(@Nullable String password) { - this.password = password; - passwordChanged = true; - return this; - } - - @CheckForNull - public ExternalIdentity externalIdentity() { - return externalIdentity; - } - - /** - * This method should only be used when updating a none local user - */ - public UpdateUser setExternalIdentity(@Nullable ExternalIdentity externalIdentity) { - this.externalIdentity = externalIdentity; - externalIdentityChanged = true; - return this; - } - - public boolean isLoginChanged() { - return loginChanged; - } - - public boolean isNameChanged() { - return nameChanged; - } - - public boolean isEmailChanged() { - return emailChanged; - } - - public boolean isScmAccountsChanged() { - return scmAccountsChanged; - } - - public boolean isPasswordChanged() { - return passwordChanged; - } - - public boolean isExternalIdentityChanged() { - return externalIdentityChanged; - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java deleted file mode 100644 index 3399e36cdcc..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import javax.annotation.CheckForNull; -import javax.annotation.concurrent.Immutable; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.db.user.GroupDto; - -import static java.util.Objects.requireNonNull; - -public interface UserSession { - - /** - * Login of the authenticated user. Returns {@code null} - * if {@link #isLoggedIn()} is {@code false}. - */ - @CheckForNull - String getLogin(); - - /** - * Uuid of the authenticated user. Returns {@code null} - * if {@link #isLoggedIn()} is {@code false}. - */ - @CheckForNull - String getUuid(); - - /** - * Name of the authenticated user. Returns {@code null} - * if {@link #isLoggedIn()} is {@code false}. - */ - @CheckForNull - String getName(); - - /** - * Database ID of the authenticated user. Returns {@code null} - * if {@link #isLoggedIn()} is {@code false}. - */ - @CheckForNull - Integer getUserId(); - - /** - * The groups that the logged-in user is member of. An empty - * collection is returned if {@link #isLoggedIn()} is {@code false}. - */ - Collection<GroupDto> getGroups(); - - /** - * This enum supports by name only the few providers for which specific code exists. - */ - enum IdentityProvider { - SONARQUBE("sonarqube"), GITHUB("github"), BITBUCKETCLOUD("bitbucket"), OTHER("other"); - - String key; - - IdentityProvider(String key) { - this.key = key; - } - - public String getKey() { - return key; - } - - public static IdentityProvider getFromKey(String key) { - return Arrays.stream(IdentityProvider.values()) - .filter(i -> i.getKey().equals(key)) - .findAny() - .orElse(OTHER); - } - } - - /** - * @return empty if user is anonymous - */ - Optional<IdentityProvider> getIdentityProvider(); - - @Immutable - final class ExternalIdentity { - private final String id; - private final String login; - - public ExternalIdentity(String id, String login) { - this.id = requireNonNull(id, "id can't be null"); - this.login = requireNonNull(login, "login can't be null"); - } - - public String getId() { - return id; - } - - public String getLogin() { - return login; - } - - @Override - public String toString() { - return "ExternalIdentity{" + - "id='" + id + '\'' + - ", login='" + login + '\'' + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ExternalIdentity that = (ExternalIdentity) o; - return Objects.equals(id, that.id) && - Objects.equals(login, that.login); - } - - @Override - public int hashCode() { - return Objects.hash(id, login); - } - } - - /** - * @return empty if {@link #getIdentityProvider()} returns empty or {@link IdentityProvider#SONARQUBE} - */ - Optional<ExternalIdentity> getExternalIdentity(); - - /** - * Whether the user is logged-in or anonymous. - */ - boolean isLoggedIn(); - - /** - * Whether the user has root privileges. If {@code true}, then user automatically - * benefits from all the permissions on all organizations and projects. - */ - boolean isRoot(); - - /** - * Ensures that {@link #isRoot()} returns {@code true} otherwise throws a - * {@link org.sonar.server.exceptions.ForbiddenException}. - */ - UserSession checkIsRoot(); - - /** - * Ensures that user is logged in otherwise throws {@link org.sonar.server.exceptions.UnauthorizedException}. - */ - UserSession checkLoggedIn(); - - /** - * Returns {@code true} if the permission is granted on the organization, otherwise {@code false}. - * - * If the organization does not exist, then returns {@code false}. - * - * Always returns {@code true} if {@link #isRoot()} is {@code true}, even if - * organization does not exist. - */ - boolean hasPermission(OrganizationPermission permission, OrganizationDto organization); - - boolean hasPermission(OrganizationPermission permission, String organizationUuid); - - /** - * Ensures that {@link #hasPermission(OrganizationPermission, OrganizationDto)} is {@code true}, - * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}. - */ - UserSession checkPermission(OrganizationPermission permission, OrganizationDto organization); - - UserSession checkPermission(OrganizationPermission permission, String organizationUuid); - - /** - * Returns {@code true} if the permission is granted to user on the component, - * otherwise {@code false}. - * - * If the component does not exist, then returns {@code false}. - * - * Always returns {@code true} if {@link #isRoot()} is {@code true}, even if - * component does not exist. - * - * If the permission is not granted, then the organization permission is _not_ checked. - * - * @param component non-null component. - * @param permission project permission as defined by {@link org.sonar.server.permission.PermissionService} - */ - boolean hasComponentPermission(String permission, ComponentDto component); - - /** - * Using {@link #hasComponentPermission(String, ComponentDto)} is recommended - * because it does not have to load project if the referenced component - * is not a project. - * - * @deprecated use {@link #hasComponentPermission(String, ComponentDto)} instead - */ - @Deprecated - boolean hasComponentUuidPermission(String permission, String componentUuid); - - /** - * Return the subset of specified components which the user has granted permission. - * An empty list is returned if input is empty or if no components are allowed to be - * accessed. - * If the input is ordered, then the returned components are in the same order. - * The duplicated components are returned duplicated too. - */ - List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components); - - /** - * Ensures that {@link #hasComponentPermission(String, ComponentDto)} is {@code true}, - * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}. - */ - UserSession checkComponentPermission(String projectPermission, ComponentDto component); - - /** - * Ensures that {@link #hasComponentUuidPermission(String, String)} is {@code true}, - * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}. - * - * @deprecated use {@link #checkComponentPermission(String, ComponentDto)} instead - */ - @Deprecated - UserSession checkComponentUuidPermission(String permission, String componentUuid); - - /** - * Whether user can administrate system, for example for using cross-organizations services - * like update center, system info or management of users. - * - * Returns {@code true} if: - * <ul> - * <li>{@link #isRoot()} is {@code true}</li> - * <li>organization feature is disabled and user is administrator of the (single) default organization</li> - * </ul> - */ - boolean isSystemAdministrator(); - - /** - * Ensures that {@link #isSystemAdministrator()} is {@code true}, - * otherwise throws {@link org.sonar.server.exceptions.ForbiddenException}. - */ - UserSession checkIsSystemAdministrator(); - - /** - * Returns {@code true} if the user is member of the organization, otherwise {@code false}. - * - * If the organization does not exist, then returns {@code false}. - * - * Always returns {@code true} if {@link #isRoot()} is {@code true}, even if - * organization does not exist. - */ - boolean hasMembership(OrganizationDto organization); - - /** - * Ensures that {@link #hasMembership(OrganizationDto)} is {@code true}, - * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}. - */ - UserSession checkMembership(OrganizationDto organization); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactory.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactory.java deleted file mode 100644 index 6c9500a4769..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactory.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.sonar.api.server.ServerSide; -import org.sonar.db.user.UserDto; - -@ServerSide -public interface UserSessionFactory { - - UserSession create(UserDto user); - - UserSession createAnonymous(); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java deleted file mode 100644 index 5574be514e8..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.sonar.api.server.ServerSide; -import org.sonar.db.DbClient; -import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.UserLastConnectionDatesUpdater; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.OrganizationFlags; - -import static java.util.Objects.requireNonNull; - -@ServerSide -public class UserSessionFactoryImpl implements UserSessionFactory { - - private final DbClient dbClient; - private final DefaultOrganizationProvider defaultOrganizationProvider; - private final OrganizationFlags organizationFlags; - private final UserLastConnectionDatesUpdater userLastConnectionDatesUpdater; - - public UserSessionFactoryImpl(DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider, - OrganizationFlags organizationFlags, UserLastConnectionDatesUpdater userLastConnectionDatesUpdater) { - this.dbClient = dbClient; - this.defaultOrganizationProvider = defaultOrganizationProvider; - this.organizationFlags = organizationFlags; - this.userLastConnectionDatesUpdater = userLastConnectionDatesUpdater; - } - - @Override - public ServerUserSession create(UserDto user) { - requireNonNull(user, "UserDto must not be null"); - userLastConnectionDatesUpdater.updateLastConnectionDateIfNeeded(user); - return new ServerUserSession(dbClient, organizationFlags, defaultOrganizationProvider, user); - } - - @Override - public ServerUserSession createAnonymous() { - return new ServerUserSession(dbClient, organizationFlags, defaultOrganizationProvider, null); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java deleted file mode 100644 index 4c41bbd9ff7..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java +++ /dev/null @@ -1,485 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import com.google.common.base.Joiner; -import com.google.common.base.Strings; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.stream.Stream; -import javax.annotation.Nullable; -import org.apache.commons.lang.math.RandomUtils; -import org.sonar.api.config.Configuration; -import org.sonar.api.platform.NewUserHandler; -import org.sonar.api.server.ServerSide; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.organization.OrganizationMemberDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserGroupDto; -import org.sonar.db.user.UserPropertyDto; -import org.sonar.server.authentication.CredentialsLocalAuthentication; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.OrganizationFlags; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.usergroups.DefaultGroupFinder; -import org.sonar.server.util.Validation; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Strings.isNullOrEmpty; -import static com.google.common.collect.Lists.newArrayList; -import static java.lang.String.format; -import static java.util.Arrays.stream; -import static java.util.stream.Stream.concat; -import static org.sonar.api.CoreProperties.DEFAULT_ISSUE_ASSIGNEE; -import static org.sonar.core.util.Slug.slugify; -import static org.sonar.core.util.stream.MoreCollectors.toList; -import static org.sonar.process.ProcessProperties.Property.ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS; -import static org.sonar.server.exceptions.BadRequestException.checkRequest; - -@ServerSide -public class UserUpdater { - - public static final String NOTIFICATIONS_READ_DATE = "notifications.readDate"; - - private static final String SQ_AUTHORITY = "sonarqube"; - - private static final String LOGIN_PARAM = "Login"; - private static final String PASSWORD_PARAM = "Password"; - private static final String NAME_PARAM = "Name"; - private static final String EMAIL_PARAM = "Email"; - - public static final int LOGIN_MIN_LENGTH = 2; - public static final int LOGIN_MAX_LENGTH = 255; - public static final int EMAIL_MAX_LENGTH = 100; - public static final int NAME_MAX_LENGTH = 200; - - private final NewUserNotifier newUserNotifier; - private final DbClient dbClient; - private final UserIndexer userIndexer; - private final DefaultOrganizationProvider defaultOrganizationProvider; - private final OrganizationFlags organizationFlags; - private final DefaultGroupFinder defaultGroupFinder; - private final Configuration config; - private final CredentialsLocalAuthentication localAuthentication; - private final System2 system2; - - public UserUpdater(System2 system2, NewUserNotifier newUserNotifier, DbClient dbClient, UserIndexer userIndexer, OrganizationFlags organizationFlags, - DefaultOrganizationProvider defaultOrganizationProvider, DefaultGroupFinder defaultGroupFinder, Configuration config, - CredentialsLocalAuthentication localAuthentication) { - this.system2 = system2; - this.newUserNotifier = newUserNotifier; - this.dbClient = dbClient; - this.userIndexer = userIndexer; - this.organizationFlags = organizationFlags; - this.defaultOrganizationProvider = defaultOrganizationProvider; - this.defaultGroupFinder = defaultGroupFinder; - this.config = config; - this.localAuthentication = localAuthentication; - } - - public UserDto createAndCommit(DbSession dbSession, NewUser newUser, Consumer<UserDto> beforeCommit, UserDto... otherUsersToIndex) { - UserDto userDto = saveUser(dbSession, createDto(dbSession, newUser)); - return commitUser(dbSession, userDto, beforeCommit, otherUsersToIndex); - } - - public UserDto reactivateAndCommit(DbSession dbSession, UserDto disabledUser, NewUser newUser, Consumer<UserDto> beforeCommit, UserDto... otherUsersToIndex) { - checkArgument(!disabledUser.isActive(), "An active user with login '%s' already exists", disabledUser.getLogin()); - reactivateUser(dbSession, disabledUser, newUser); - return commitUser(dbSession, disabledUser, beforeCommit, otherUsersToIndex); - } - - private void reactivateUser(DbSession dbSession, UserDto reactivatedUser, NewUser newUser) { - UpdateUser updateUser = new UpdateUser() - .setName(newUser.name()) - .setEmail(newUser.email()) - .setScmAccounts(newUser.scmAccounts()) - .setExternalIdentity(newUser.externalIdentity()); - String login = newUser.login(); - if (login != null) { - updateUser.setLogin(login); - } - String password = newUser.password(); - if (password != null) { - updateUser.setPassword(password); - } - setOnboarded(reactivatedUser); - updateDto(dbSession, updateUser, reactivatedUser); - updateUser(dbSession, reactivatedUser); - boolean isOrganizationEnabled = organizationFlags.isEnabled(dbSession); - if (isOrganizationEnabled) { - setNotificationsReadDate(dbSession, reactivatedUser); - } else { - addUserToDefaultOrganizationAndDefaultGroup(dbSession, reactivatedUser); - } - } - - public void updateAndCommit(DbSession dbSession, UserDto dto, UpdateUser updateUser, Consumer<UserDto> beforeCommit, UserDto... otherUsersToIndex) { - boolean isUserUpdated = updateDto(dbSession, updateUser, dto); - if (isUserUpdated) { - // at least one change. Database must be updated and Elasticsearch re-indexed - updateUser(dbSession, dto); - commitUser(dbSession, dto, beforeCommit, otherUsersToIndex); - } else { - // no changes but still execute the consumer - beforeCommit.accept(dto); - dbSession.commit(); - } - } - - private UserDto commitUser(DbSession dbSession, UserDto userDto, Consumer<UserDto> beforeCommit, UserDto... otherUsersToIndex) { - beforeCommit.accept(userDto); - userIndexer.commitAndIndex(dbSession, concat(Stream.of(userDto), stream(otherUsersToIndex)).collect(toList())); - notifyNewUser(userDto.getLogin(), userDto.getName(), userDto.getEmail()); - return userDto; - } - - private UserDto createDto(DbSession dbSession, NewUser newUser) { - UserDto userDto = new UserDto(); - List<String> messages = new ArrayList<>(); - - String login = newUser.login(); - if (isNullOrEmpty(login)) { - userDto.setLogin(generateUniqueLogin(dbSession, newUser.name())); - } else if (validateLoginFormat(login, messages)) { - checkLoginUniqueness(dbSession, login); - userDto.setLogin(login); - } - - String name = newUser.name(); - if (validateNameFormat(name, messages)) { - userDto.setName(name); - } - - String email = newUser.email(); - if (email != null && validateEmailFormat(email, messages)) { - userDto.setEmail(email); - } - - String password = newUser.password(); - if (password != null && validatePasswords(password, messages)) { - localAuthentication.storeHashPassword(userDto, password); - } - - List<String> scmAccounts = sanitizeScmAccounts(newUser.scmAccounts()); - if (scmAccounts != null && !scmAccounts.isEmpty() && validateScmAccounts(dbSession, scmAccounts, login, email, null, messages)) { - userDto.setScmAccounts(scmAccounts); - } - - setExternalIdentity(dbSession, userDto, newUser.externalIdentity()); - setOnboarded(userDto); - - checkRequest(messages.isEmpty(), messages); - return userDto; - } - - private String generateUniqueLogin(DbSession dbSession, String userName) { - String slugName = slugify(userName); - for (int i = 0; i < 10; i++) { - String login = slugName + RandomUtils.nextInt(100_000); - UserDto existingUser = dbClient.userDao().selectByLogin(dbSession, login); - if (existingUser == null) { - return login; - } - } - throw new IllegalStateException("Cannot create unique login for user name " + userName); - } - - private boolean updateDto(DbSession dbSession, UpdateUser update, UserDto dto) { - List<String> messages = newArrayList(); - boolean changed = updateLogin(dbSession, update, dto, messages); - changed |= updateName(update, dto, messages); - changed |= updateEmail(update, dto, messages); - changed |= updateExternalIdentity(dbSession, update, dto); - changed |= updatePassword(update, dto, messages); - changed |= updateScmAccounts(dbSession, update, dto, messages); - checkRequest(messages.isEmpty(), messages); - return changed; - } - - private boolean updateLogin(DbSession dbSession, UpdateUser updateUser, UserDto userDto, List<String> messages) { - String newLogin = updateUser.login(); - if (!updateUser.isLoginChanged() || !validateLoginFormat(newLogin, messages) || Objects.equals(userDto.getLogin(), newLogin)) { - return false; - } - checkLoginUniqueness(dbSession, newLogin); - dbClient.propertiesDao().selectByKeyAndMatchingValue(dbSession, DEFAULT_ISSUE_ASSIGNEE, userDto.getLogin()) - .forEach(p -> dbClient.propertiesDao().saveProperty(p.setValue(newLogin))); - userDto.setLogin(newLogin); - if (userDto.isLocal()) { - userDto.setExternalLogin(newLogin); - userDto.setExternalId(newLogin); - } - return true; - } - - private static boolean updateName(UpdateUser updateUser, UserDto userDto, List<String> messages) { - String name = updateUser.name(); - if (updateUser.isNameChanged() && validateNameFormat(name, messages) && !Objects.equals(userDto.getName(), name)) { - userDto.setName(name); - return true; - } - return false; - } - - private static boolean updateEmail(UpdateUser updateUser, UserDto userDto, List<String> messages) { - String email = updateUser.email(); - if (updateUser.isEmailChanged() && validateEmailFormat(email, messages) && !Objects.equals(userDto.getEmail(), email)) { - userDto.setEmail(email); - return true; - } - return false; - } - - private boolean updateExternalIdentity(DbSession dbSession, UpdateUser updateUser, UserDto userDto) { - ExternalIdentity externalIdentity = updateUser.externalIdentity(); - if (updateUser.isExternalIdentityChanged() && !isSameExternalIdentity(userDto, externalIdentity)) { - setExternalIdentity(dbSession, userDto, externalIdentity); - return true; - } - return false; - } - - private boolean updatePassword(UpdateUser updateUser, UserDto userDto, List<String> messages) { - String password = updateUser.password(); - if (updateUser.isPasswordChanged() && validatePasswords(password, messages) && checkPasswordChangeAllowed(userDto, messages)) { - localAuthentication.storeHashPassword(userDto, password); - return true; - } - return false; - } - - private boolean updateScmAccounts(DbSession dbSession, UpdateUser updateUser, UserDto userDto, List<String> messages) { - String email = updateUser.email(); - List<String> scmAccounts = sanitizeScmAccounts(updateUser.scmAccounts()); - List<String> existingScmAccounts = userDto.getScmAccountsAsList(); - if (updateUser.isScmAccountsChanged() && !(existingScmAccounts.containsAll(scmAccounts) && scmAccounts.containsAll(existingScmAccounts))) { - if (!scmAccounts.isEmpty()) { - String newOrOldEmail = email != null ? email : userDto.getEmail(); - if (validateScmAccounts(dbSession, scmAccounts, userDto.getLogin(), newOrOldEmail, userDto, messages)) { - userDto.setScmAccounts(scmAccounts); - } - } else { - userDto.setScmAccounts((String) null); - } - return true; - } - return false; - } - - private static boolean isSameExternalIdentity(UserDto dto, @Nullable ExternalIdentity externalIdentity) { - return externalIdentity != null - && !dto.isLocal() - && Objects.equals(dto.getExternalId(), externalIdentity.getId()) - && Objects.equals(dto.getExternalLogin(), externalIdentity.getLogin()) - && Objects.equals(dto.getExternalIdentityProvider(), externalIdentity.getProvider()); - } - - private void setExternalIdentity(DbSession dbSession, UserDto dto, @Nullable ExternalIdentity externalIdentity) { - if (externalIdentity == null) { - dto.setExternalLogin(dto.getLogin()); - dto.setExternalIdentityProvider(SQ_AUTHORITY); - dto.setExternalId(dto.getLogin()); - dto.setLocal(true); - } else { - dto.setExternalLogin(externalIdentity.getLogin()); - dto.setExternalIdentityProvider(externalIdentity.getProvider()); - dto.setExternalId(externalIdentity.getId()); - dto.setLocal(false); - dto.setSalt(null); - dto.setCryptedPassword(null); - } - UserDto existingUser = dbClient.userDao().selectByExternalIdAndIdentityProvider(dbSession, dto.getExternalId(), dto.getExternalIdentityProvider()); - checkArgument(existingUser == null || Objects.equals(dto.getUuid(), existingUser.getUuid()), - "A user with provider id '%s' and identity provider '%s' already exists", dto.getExternalId(), dto.getExternalIdentityProvider()); - } - - private void setOnboarded(UserDto userDto) { - boolean showOnboarding = config.getBoolean(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS.getKey()).orElse(false); - userDto.setOnboarded(!showOnboarding); - } - - private static boolean checkNotEmptyParam(@Nullable String value, String param, List<String> messages) { - if (isNullOrEmpty(value)) { - messages.add(format(Validation.CANT_BE_EMPTY_MESSAGE, param)); - return false; - } - return true; - } - - private static boolean validateLoginFormat(@Nullable String login, List<String> messages) { - boolean isValid = checkNotEmptyParam(login, LOGIN_PARAM, messages); - if (!isNullOrEmpty(login)) { - if (login.length() < LOGIN_MIN_LENGTH) { - messages.add(format(Validation.IS_TOO_SHORT_MESSAGE, LOGIN_PARAM, LOGIN_MIN_LENGTH)); - return false; - } else if (login.length() > LOGIN_MAX_LENGTH) { - messages.add(format(Validation.IS_TOO_LONG_MESSAGE, LOGIN_PARAM, LOGIN_MAX_LENGTH)); - return false; - } else if (!login.matches("\\A\\w[\\w\\.\\-_@]+\\z")) { - messages.add("Use only letters, numbers, and .-_@ please."); - return false; - } - } - return isValid; - } - - private static boolean validateNameFormat(@Nullable String name, List<String> messages) { - boolean isValid = checkNotEmptyParam(name, NAME_PARAM, messages); - if (name != null && name.length() > NAME_MAX_LENGTH) { - messages.add(format(Validation.IS_TOO_LONG_MESSAGE, NAME_PARAM, 200)); - return false; - } - return isValid; - } - - private static boolean validateEmailFormat(@Nullable String email, List<String> messages) { - if (email != null && email.length() > EMAIL_MAX_LENGTH) { - messages.add(format(Validation.IS_TOO_LONG_MESSAGE, EMAIL_PARAM, 100)); - return false; - } - return true; - } - - private static boolean checkPasswordChangeAllowed(UserDto userDto, List<String> messages) { - if (!userDto.isLocal()) { - messages.add("Password cannot be changed when external authentication is used"); - return false; - } - return true; - } - - private static boolean validatePasswords(@Nullable String password, List<String> messages) { - if (password == null || password.length() == 0) { - messages.add(format(Validation.CANT_BE_EMPTY_MESSAGE, PASSWORD_PARAM)); - return false; - } - return true; - } - - private boolean validateScmAccounts(DbSession dbSession, List<String> scmAccounts, @Nullable String login, @Nullable String email, @Nullable UserDto existingUser, - List<String> messages) { - boolean isValid = true; - for (String scmAccount : scmAccounts) { - if (scmAccount.equals(login) || scmAccount.equals(email)) { - messages.add("Login and email are automatically considered as SCM accounts"); - isValid = false; - } else { - List<UserDto> matchingUsers = dbClient.userDao().selectByScmAccountOrLoginOrEmail(dbSession, scmAccount); - List<String> matchingUsersWithoutExistingUser = newArrayList(); - for (UserDto matchingUser : matchingUsers) { - if (existingUser != null && matchingUser.getId().equals(existingUser.getId())) { - continue; - } - matchingUsersWithoutExistingUser.add(getNameOrLogin(matchingUser) + " (" + matchingUser.getLogin() + ")"); - } - if (!matchingUsersWithoutExistingUser.isEmpty()) { - messages.add(format("The scm account '%s' is already used by user(s) : '%s'", scmAccount, Joiner.on(", ").join(matchingUsersWithoutExistingUser))); - isValid = false; - } - } - } - return isValid; - } - - private static String getNameOrLogin(UserDto user) { - String name = user.getName(); - return name != null ? name : user.getLogin(); - } - - private static List<String> sanitizeScmAccounts(@Nullable List<String> scmAccounts) { - if (scmAccounts != null) { - return new HashSet<>(scmAccounts).stream() - .map(Strings::emptyToNull) - .filter(Objects::nonNull) - .sorted(String::compareToIgnoreCase) - .collect(toList(scmAccounts.size())); - } - return Collections.emptyList(); - } - - private void checkLoginUniqueness(DbSession dbSession, String login) { - UserDto existingUser = dbClient.userDao().selectByLogin(dbSession, login); - checkArgument(existingUser == null, "A user with login '%s' already exists", login); - } - - private UserDto saveUser(DbSession dbSession, UserDto userDto) { - userDto.setActive(true); - UserDto res = dbClient.userDao().insert(dbSession, userDto); - boolean isOrganizationEnabled = organizationFlags.isEnabled(dbSession); - if (isOrganizationEnabled) { - setNotificationsReadDate(dbSession, userDto); - } else { - addUserToDefaultOrganizationAndDefaultGroup(dbSession, userDto); - } - - return res; - } - - private void updateUser(DbSession dbSession, UserDto dto) { - dto.setActive(true); - dbClient.userDao().update(dbSession, dto); - } - - private void notifyNewUser(String login, String name, @Nullable String email) { - newUserNotifier.onNewUser(NewUserHandler.Context.builder() - .setLogin(login) - .setName(name) - .setEmail(email) - .build()); - } - - private static boolean isUserAlreadyMemberOfDefaultGroup(GroupDto defaultGroup, List<GroupDto> userGroups) { - return userGroups.stream().anyMatch(group -> defaultGroup.getId().equals(group.getId())); - } - - private void addUserToDefaultOrganizationAndDefaultGroup(DbSession dbSession, UserDto userDto) { - addUserToDefaultOrganization(dbSession, userDto); - addDefaultGroup(dbSession, userDto); - } - - private void addUserToDefaultOrganization(DbSession dbSession, UserDto userDto) { - String defOrgUuid = defaultOrganizationProvider.get().getUuid(); - dbClient.organizationMemberDao().insert(dbSession, new OrganizationMemberDto().setOrganizationUuid(defOrgUuid).setUserId(userDto.getId())); - } - - private void addDefaultGroup(DbSession dbSession, UserDto userDto) { - String defOrgUuid = defaultOrganizationProvider.get().getUuid(); - List<GroupDto> userGroups = dbClient.groupDao().selectByUserLogin(dbSession, userDto.getLogin()); - GroupDto defaultGroup = defaultGroupFinder.findDefaultGroup(dbSession, defOrgUuid); - if (isUserAlreadyMemberOfDefaultGroup(defaultGroup, userGroups)) { - return; - } - dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setUserId(userDto.getId()).setGroupId(defaultGroup.getId())); - } - - private void setNotificationsReadDate(DbSession dbSession, UserDto user) { - dbClient.userPropertiesDao().insertOrUpdate(dbSession, new UserPropertyDto() - .setUserUuid(user.getUuid()) - .setKey(NOTIFICATIONS_READ_DATE) - .setValue(Long.toString(system2.now()))); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/user/package-info.java deleted file mode 100644 index 15949569de4..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/user/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.user; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/usergroups/DefaultGroupCreator.java b/server/sonar-server/src/main/java/org/sonar/server/usergroups/DefaultGroupCreator.java deleted file mode 100644 index b6fa36c1225..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/usergroups/DefaultGroupCreator.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usergroups; - -import org.sonar.db.DbSession; -import org.sonar.db.user.GroupDto; - -public interface DefaultGroupCreator { - - /** - * Create the default group on the given organization - */ - GroupDto create(DbSession dbSession, String organizationUuid); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/usergroups/DefaultGroupCreatorImpl.java b/server/sonar-server/src/main/java/org/sonar/server/usergroups/DefaultGroupCreatorImpl.java deleted file mode 100644 index 4d7e1836a11..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/usergroups/DefaultGroupCreatorImpl.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usergroups; - -import java.util.Optional; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.user.GroupDto; - -import static com.google.common.base.Preconditions.checkArgument; - -public class DefaultGroupCreatorImpl implements DefaultGroupCreator { - - static final String DEFAULT_GROUP_NAME = "Members"; - private final DbClient dbClient; - - public DefaultGroupCreatorImpl(DbClient dbClient) { - this.dbClient = dbClient; - } - - public GroupDto create(DbSession dbSession, String organizationUuid) { - Optional<GroupDto> existingMembersGroup = dbClient.groupDao().selectByName(dbSession, organizationUuid, DEFAULT_GROUP_NAME); - checkArgument(!existingMembersGroup.isPresent(), "The group '%s' already exist on organization '%s'", DEFAULT_GROUP_NAME, organizationUuid); - - GroupDto defaultGroup = new GroupDto() - .setName(DEFAULT_GROUP_NAME) - .setDescription("All members of the organization") - .setOrganizationUuid(organizationUuid); - dbClient.groupDao().insert(dbSession, defaultGroup); - dbClient.organizationDao().setDefaultGroupId(dbSession, organizationUuid, defaultGroup); - return defaultGroup; - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/usergroups/DefaultGroupFinder.java b/server/sonar-server/src/main/java/org/sonar/server/usergroups/DefaultGroupFinder.java deleted file mode 100644 index ec4fc062633..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/usergroups/DefaultGroupFinder.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usergroups; - -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.user.GroupDto; - -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; - -public class DefaultGroupFinder { - - private final DbClient dbClient; - - public DefaultGroupFinder(DbClient dbClient) { - this.dbClient = dbClient; - } - - public GroupDto findDefaultGroup(DbSession dbSession, String organizationUuid) { - int defaultGroupId = dbClient.organizationDao().getDefaultGroupId(dbSession, organizationUuid) - .orElseThrow(() -> new IllegalStateException(format("Default group cannot be found on organization '%s'", organizationUuid))); - return requireNonNull(dbClient.groupDao().selectById(dbSession, defaultGroupId), format("Group '%s' cannot be found", defaultGroupId)); - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/usergroups/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/usergroups/package-info.java deleted file mode 100644 index 7d41d8701a5..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/usergroups/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.usergroups; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGenerator.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGenerator.java deleted file mode 100644 index 41f9d5f7665..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGenerator.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usertoken; - -public interface TokenGenerator { - /** - * Generate a token. It must be unique and non deterministic.<br /> - * Underlying algorithm, format and max length are - * subject to change in subsequent SonarQube versions. - * <br/> - * Length does not exceed 40 characters (arbitrary value). - * Token is composed of hexadecimal characters only (0-9, a-f) - * <br/> - * The token is sent through the userid field (login) of HTTP Basic authentication, - * - * Basic authentication is used to authenticate users from tokens, so the - * constraints of userid field (login) must be respected. Basically the token - * must not contain colon character ":". - * - */ - String generate(); - - /** - * Hash a token.<br/> - * Underlying algorithm, format and max length are - * subject to change in subsequent SonarQube versions. - * <br /> - * Length must not exceed 255 characters. - * Hash is composed of hexadecimal characters only (0-9, a-f) - */ - String hash(String token); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGeneratorImpl.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGeneratorImpl.java deleted file mode 100644 index 16ccabb009d..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGeneratorImpl.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usertoken; - -import java.security.SecureRandom; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.codec.digest.DigestUtils; - -public class TokenGeneratorImpl implements TokenGenerator { - @Override - public String generate() { - SecureRandom random = new SecureRandom(); - byte[] bytes = new byte[20]; - random.nextBytes(bytes); - return Hex.encodeHexString(bytes); - } - - @Override - public String hash(String token) { - return DigestUtils.sha384Hex(token); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java deleted file mode 100644 index f3fdb952c94..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usertoken; - -import java.util.Optional; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.user.UserTokenDto; -import org.sonar.server.authentication.UserLastConnectionDatesUpdater; - -import static java.util.Optional.empty; -import static java.util.Optional.of; - -public class UserTokenAuthentication { - private final TokenGenerator tokenGenerator; - private final DbClient dbClient; - private final UserLastConnectionDatesUpdater userLastConnectionDatesUpdater; - - public UserTokenAuthentication(TokenGenerator tokenGenerator, DbClient dbClient, UserLastConnectionDatesUpdater userLastConnectionDatesUpdater) { - this.tokenGenerator = tokenGenerator; - this.dbClient = dbClient; - this.userLastConnectionDatesUpdater = userLastConnectionDatesUpdater; - } - - /** - * Returns the user uuid if the token hash is found, else {@code Optional.absent()}. - * The returned uuid is not validated. If database is corrupted (table USER_TOKENS badly purged - * for instance), then the uuid may not relate to a valid user. - */ - public Optional<String> authenticate(String token) { - String tokenHash = tokenGenerator.hash(token); - try (DbSession dbSession = dbClient.openSession(false)) { - UserTokenDto userToken = dbClient.userTokenDao().selectByTokenHash(dbSession, tokenHash); - if (userToken == null) { - return empty(); - } - userLastConnectionDatesUpdater.updateLastConnectionDateIfNeeded(userToken); - return of(userToken.getUserUuid()); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java deleted file mode 100644 index 7f3bd962a68..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usertoken; - -import org.sonar.core.platform.Module; - -public class UserTokenModule extends Module { - @Override - protected void configureModule() { - add( - UserTokenAuthentication.class, - TokenGeneratorImpl.class); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/package-info.java deleted file mode 100644 index 449f4017a05..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/package-info.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.usertoken; - -import javax.annotation.ParametersAreNonnullByDefault; - diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/BooleanTypeValidation.java b/server/sonar-server/src/main/java/org/sonar/server/util/BooleanTypeValidation.java deleted file mode 100644 index 1b3ade32e60..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/BooleanTypeValidation.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import java.util.List; -import javax.annotation.Nullable; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.PropertyType; - -import static org.sonar.server.exceptions.BadRequestException.checkRequest; - -public class BooleanTypeValidation implements TypeValidation { - - @Override - public String key() { - return PropertyType.BOOLEAN.name(); - } - - @Override - public void validate(String value, @Nullable List<String> options) { - checkRequest(StringUtils.equalsIgnoreCase(value, "true") || StringUtils.equalsIgnoreCase(value, "false"), - "Value '%s' must be one of \"true\" or \"false\".", value); - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/FloatTypeValidation.java b/server/sonar-server/src/main/java/org/sonar/server/util/FloatTypeValidation.java deleted file mode 100644 index fd221dced55..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/FloatTypeValidation.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import java.util.List; -import javax.annotation.Nullable; -import org.sonar.api.PropertyType; -import org.sonar.server.exceptions.BadRequestException; - -import static java.lang.String.format; - -public class FloatTypeValidation implements TypeValidation { - - @Override - public String key() { - return PropertyType.FLOAT.name(); - } - - @Override - public void validate(String value, @Nullable List<String> options) { - try { - Double.parseDouble(value); - } catch (NumberFormatException e) { - throw BadRequestException.create(format("Value '%s' must be an floating point number.", value)); - } - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/IntegerTypeValidation.java b/server/sonar-server/src/main/java/org/sonar/server/util/IntegerTypeValidation.java deleted file mode 100644 index 84502df47f7..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/IntegerTypeValidation.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import java.util.List; -import javax.annotation.Nullable; -import org.sonar.api.PropertyType; -import org.sonar.server.exceptions.BadRequestException; - -import static java.lang.String.format; - -public class IntegerTypeValidation implements TypeValidation { - - @Override - public String key() { - return PropertyType.INTEGER.name(); - } - - @Override - public void validate(String value, @Nullable List<String> options) { - try { - Integer.parseInt(value); - } catch (NumberFormatException e) { - throw BadRequestException.create(format("Value '%s' must be an integer.", value)); - } - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/LongTypeValidation.java b/server/sonar-server/src/main/java/org/sonar/server/util/LongTypeValidation.java deleted file mode 100644 index 9ca540a1d1d..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/LongTypeValidation.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import java.util.List; -import javax.annotation.Nullable; -import org.sonar.api.PropertyType; -import org.sonar.server.exceptions.BadRequestException; - -import static java.lang.String.format; - -public class LongTypeValidation implements TypeValidation { - @Override - public String key() { - return PropertyType.LONG.name(); - } - - @Override - public void validate(String value, @Nullable List<String> options) { - try { - Long.parseLong(value); - } catch (NumberFormatException e) { - throw BadRequestException.create(format("Value '%s' must be a long.", value)); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/MetricKeyValidator.java b/server/sonar-server/src/main/java/org/sonar/server/util/MetricKeyValidator.java deleted file mode 100644 index 275341cf1cf..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/MetricKeyValidator.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -public class MetricKeyValidator { - /* - * Allowed characters are alphanumeric, '-', '_', with at least one non-digit - */ - private static final String VALID_METRIC_KEY_REGEXP = "[\\p{Alnum}\\-_]*[\\p{Alpha}\\-_]+[\\p{Alnum}\\-_]*"; - - private MetricKeyValidator() { - // static stuff only - } - - /** - * <p>Test if given parameter is valid for a project/module. Valid format is:</p> - * <ul> - * <li>Allowed characters: - * <ul> - * <li>Uppercase ASCII letters A-Z</li> - * <li>Lowercase ASCII letters a-z</li> - * <li>ASCII digits 0-9</li> - * <li>Punctuation signs dash '-', underscore '_'</li> - * </ul> - * </li> - * <li>At least one non-digit</li> - * </ul> - * @param candidateKey - * @return <code>true</code> if <code>candidateKey</code> can be used for a metric - */ - public static boolean isMetricKeyValid(String candidateKey) { - return candidateKey.matches(VALID_METRIC_KEY_REGEXP); - } - - public static String checkMetricKeyFormat(String candidateKey) { - if (!isMetricKeyValid(candidateKey)) { - throw new IllegalArgumentException(String.format("Malformed metric key '%s'. Allowed characters are alphanumeric, '-', '_', with at least one non-digit.", - candidateKey)); - } - - return candidateKey; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/MetricLevelTypeValidation.java b/server/sonar-server/src/main/java/org/sonar/server/util/MetricLevelTypeValidation.java deleted file mode 100644 index ba8598a7786..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/MetricLevelTypeValidation.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import java.util.List; -import javax.annotation.Nullable; -import org.sonar.api.PropertyType; -import org.sonar.api.measures.Metric; -import org.sonar.server.exceptions.BadRequestException; - -import static java.lang.String.format; - -public class MetricLevelTypeValidation implements TypeValidation { - @Override - public String key() { - return PropertyType.METRIC_LEVEL.name(); - } - - @Override - public void validate(String value, @Nullable List<String> options) { - try { - Metric.Level.valueOf(value); - } catch (IllegalArgumentException e) { - throw BadRequestException.create(format("Value '%s' must be one of \"OK\", \"ERROR\".", value)); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/StringListTypeValidation.java b/server/sonar-server/src/main/java/org/sonar/server/util/StringListTypeValidation.java deleted file mode 100644 index acb1c7dca8f..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/StringListTypeValidation.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import java.util.List; -import javax.annotation.Nullable; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.PropertyType; - -import static org.sonar.server.exceptions.BadRequestException.checkRequest; - -public class StringListTypeValidation implements TypeValidation { - - @Override - public String key() { - return PropertyType.SINGLE_SELECT_LIST.name(); - } - - @Override - public void validate(String value, @Nullable List<String> options) { - checkRequest(options == null || options.contains(value), "Value '%s' must be one of : %s.", value, StringUtils.join(options, ", ")); - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/StringTypeValidation.java b/server/sonar-server/src/main/java/org/sonar/server/util/StringTypeValidation.java deleted file mode 100644 index 3851ede0e81..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/StringTypeValidation.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import org.sonar.api.PropertyType; - -import javax.annotation.Nullable; - -import java.util.List; - -public class StringTypeValidation implements TypeValidation { - - @Override - public String key() { - return PropertyType.STRING.name(); - } - - @Override - public void validate(String value, @Nullable List<String> options) { - // Nothing to do - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/TextTypeValidation.java b/server/sonar-server/src/main/java/org/sonar/server/util/TextTypeValidation.java deleted file mode 100644 index c26ad125162..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/TextTypeValidation.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import org.sonar.api.PropertyType; - -import javax.annotation.Nullable; - -import java.util.List; - -public class TextTypeValidation implements TypeValidation { - - @Override - public String key() { - return PropertyType.TEXT.name(); - } - - @Override - public void validate(String value, @Nullable List<String> options) { - // Nothing to do - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/TypeValidation.java b/server/sonar-server/src/main/java/org/sonar/server/util/TypeValidation.java deleted file mode 100644 index 797a3438d8f..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/TypeValidation.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import org.sonar.api.server.ServerSide; - -import javax.annotation.Nullable; - -import java.util.List; - -@ServerSide -public interface TypeValidation { - - String key(); - - void validate(String value, @Nullable List<String> options); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/TypeValidationModule.java b/server/sonar-server/src/main/java/org/sonar/server/util/TypeValidationModule.java deleted file mode 100644 index 3cc4f038e5e..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/TypeValidationModule.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import org.sonar.core.platform.Module; - -public class TypeValidationModule extends Module { - @Override - protected void configureModule() { - add( - TypeValidations.class, - IntegerTypeValidation.class, - FloatTypeValidation.class, - BooleanTypeValidation.class, - TextTypeValidation.class, - StringTypeValidation.class, - StringListTypeValidation.class, - LongTypeValidation.class, - MetricLevelTypeValidation.class - ); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/TypeValidations.java b/server/sonar-server/src/main/java/org/sonar/server/util/TypeValidations.java deleted file mode 100644 index 2c8883b77eb..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/TypeValidations.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; -import java.util.List; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import org.sonar.api.server.ServerSide; - -import static org.sonar.server.exceptions.BadRequestException.checkRequest; - -@ServerSide -public class TypeValidations { - - private final List<TypeValidation> typeValidationList; - - public TypeValidations(List<TypeValidation> typeValidationList) { - this.typeValidationList = typeValidationList; - } - - public void validate(List<String> values, String type, List<String> options) { - TypeValidation typeValidation = findByKey(type); - for (String value : values) { - typeValidation.validate(value, options); - } - } - - public void validate(String value, String type, @Nullable List<String> options) { - TypeValidation typeValidation = findByKey(type); - typeValidation.validate(value, options); - } - - private TypeValidation findByKey(String key) { - TypeValidation typeValidation = Iterables.find(typeValidationList, new TypeValidationMatchKey(key), null); - checkRequest(typeValidation != null, "Type '%s' is not valid.", key); - return typeValidation; - } - - private static class TypeValidationMatchKey implements Predicate<TypeValidation> { - private final String key; - - public TypeValidationMatchKey(String key) { - this.key = key; - } - - @Override - public boolean apply(@Nonnull TypeValidation input) { - return input.key().equals(key); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/Validation.java b/server/sonar-server/src/main/java/org/sonar/server/util/Validation.java deleted file mode 100644 index 99d28d0bc96..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/Validation.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -public class Validation { - - public static final String CANT_BE_EMPTY_MESSAGE = "%s can't be empty"; - public static final String IS_TOO_SHORT_MESSAGE = "%s is too short (minimum is %s characters)"; - public static final String IS_TOO_LONG_MESSAGE = "%s is too long (maximum is %s characters)"; - public static final String IS_ALREADY_USED_MESSAGE = "%s has already been taken"; - - private Validation() { - // only static methods - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticationModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticationModuleTest.java deleted file mode 100644 index 8d281d4cb01..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/AuthenticationModuleTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.junit.Test; -import org.sonar.core.platform.ComponentContainer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER; - -public class AuthenticationModuleTest { - - @Test - public void verify_count_of_added_components() { - ComponentContainer container = new ComponentContainer(); - new AuthenticationModule().configure(container); - assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 20); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/BaseContextFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/BaseContextFactoryTest.java deleted file mode 100644 index de51e18f546..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/BaseContextFactoryTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.sonar.api.platform.Server; -import org.sonar.api.server.authentication.BaseIdentityProvider; -import org.sonar.api.server.authentication.UserIdentity; -import org.sonar.db.user.UserDto; -import org.sonar.server.user.TestUserSessionFactory; -import org.sonar.server.user.ThreadLocalUserSession; -import org.sonar.server.user.UserSession; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class BaseContextFactoryTest { - - private static final String PUBLIC_ROOT_URL = "https://mydomain.com"; - - private static final UserIdentity USER_IDENTITY = UserIdentity.builder() - .setProviderId("ABCD") - .setProviderLogin("johndoo") - .setLogin("id:johndoo") - .setName("John") - .setEmail("john@email.com") - .build(); - - private ThreadLocalUserSession threadLocalUserSession = mock(ThreadLocalUserSession.class); - - private TestUserRegistrar userIdentityAuthenticator = new TestUserRegistrar(); - private Server server = mock(Server.class); - - private HttpServletRequest request = mock(HttpServletRequest.class); - private HttpServletResponse response = mock(HttpServletResponse.class); - private BaseIdentityProvider identityProvider = mock(BaseIdentityProvider.class); - private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); - private TestUserSessionFactory userSessionFactory = TestUserSessionFactory.standalone(); - - private BaseContextFactory underTest = new BaseContextFactory(userIdentityAuthenticator, server, jwtHttpHandler, threadLocalUserSession, userSessionFactory); - - @Before - public void setUp() throws Exception { - when(server.getPublicRootUrl()).thenReturn(PUBLIC_ROOT_URL); - when(identityProvider.getName()).thenReturn("GitHub"); - when(identityProvider.getKey()).thenReturn("github"); - when(request.getSession()).thenReturn(mock(HttpSession.class)); - } - - @Test - public void create_context() { - BaseIdentityProvider.Context context = underTest.newContext(request, response, identityProvider); - - assertThat(context.getRequest()).isEqualTo(request); - assertThat(context.getResponse()).isEqualTo(response); - assertThat(context.getServerBaseURL()).isEqualTo(PUBLIC_ROOT_URL); - } - - @Test - public void authenticate() { - BaseIdentityProvider.Context context = underTest.newContext(request, response, identityProvider); - ArgumentCaptor<UserDto> userArgumentCaptor = ArgumentCaptor.forClass(UserDto.class); - - context.authenticate(USER_IDENTITY); - - assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue(); - verify(threadLocalUserSession).set(any(UserSession.class)); - verify(jwtHttpHandler).generateToken(userArgumentCaptor.capture(), eq(request), eq(response)); - assertThat(userArgumentCaptor.getValue().getLogin()).isEqualTo(USER_IDENTITY.getLogin()); - assertThat(userArgumentCaptor.getValue().getExternalId()).isEqualTo(USER_IDENTITY.getProviderId()); - assertThat(userArgumentCaptor.getValue().getExternalLogin()).isEqualTo(USER_IDENTITY.getProviderLogin()); - assertThat(userArgumentCaptor.getValue().getExternalIdentityProvider()).isEqualTo("github"); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticationTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticationTest.java deleted file mode 100644 index 493f44d964e..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/BasicAuthenticationTest.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Base64; -import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserTesting; -import org.sonar.server.authentication.event.AuthenticationEvent; -import org.sonar.server.usertoken.UserTokenAuthentication; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.rules.ExpectedException.none; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; -import static org.sonar.server.authentication.event.AuthenticationEvent.Method; -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.AuthenticationEvent.Source; -import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException; - -public class BasicAuthenticationTest { - - private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder(); - - private static final String A_LOGIN = "login"; - private static final String A_PASSWORD = "password"; - private static final String CREDENTIALS_IN_BASE64 = toBase64(A_LOGIN + ":" + A_PASSWORD); - - private static final UserDto USER = UserTesting.newUserDto().setLogin(A_LOGIN); - - @Rule - public ExpectedException expectedException = none(); - - @Rule - public DbTester db = DbTester.create(System2.INSTANCE); - - private DbClient dbClient = db.getDbClient(); - - private CredentialsAuthentication credentialsAuthentication = mock(CredentialsAuthentication.class); - private UserTokenAuthentication userTokenAuthentication = mock(UserTokenAuthentication.class); - - private HttpServletRequest request = mock(HttpServletRequest.class); - - private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - - private BasicAuthentication underTest = new BasicAuthentication(dbClient, credentialsAuthentication, userTokenAuthentication, authenticationEvent); - - @Test - public void authenticate_from_basic_http_header() { - when(request.getHeader("Authorization")).thenReturn("Basic " + CREDENTIALS_IN_BASE64); - Credentials credentials = new Credentials(A_LOGIN, A_PASSWORD); - when(credentialsAuthentication.authenticate(credentials, request, BASIC)).thenReturn(USER); - - underTest.authenticate(request); - - verify(credentialsAuthentication).authenticate(credentials, request, BASIC); - verifyNoMoreInteractions(authenticationEvent); - } - - @Test - public void authenticate_from_basic_http_header_with_password_containing_semi_colon() { - String password = "!ascii-only:-)@"; - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(A_LOGIN + ":" + password)); - when(credentialsAuthentication.authenticate(new Credentials(A_LOGIN, password), request, BASIC)).thenReturn(USER); - - underTest.authenticate(request); - - verify(credentialsAuthentication).authenticate(new Credentials(A_LOGIN, password), request, BASIC); - verifyNoMoreInteractions(authenticationEvent); - } - - @Test - public void does_not_authenticate_when_no_authorization_header() { - underTest.authenticate(request); - - verifyZeroInteractions(credentialsAuthentication, authenticationEvent); - } - - @Test - public void does_not_authenticate_when_authorization_header_is_not_BASIC() { - when(request.getHeader("Authorization")).thenReturn("OTHER " + CREDENTIALS_IN_BASE64); - - underTest.authenticate(request); - - verifyZeroInteractions(credentialsAuthentication, authenticationEvent); - } - - @Test - public void fail_to_authenticate_when_no_login() { - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(":" + A_PASSWORD)); - - expectedException.expect(authenticationException().from(Source.local(BASIC)).withoutLogin().andNoPublicMessage()); - try { - underTest.authenticate(request); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - @Test - public void fail_to_authenticate_when_invalid_header() { - when(request.getHeader("Authorization")).thenReturn("Basic Invàlid"); - - expectedException.expect(authenticationException().from(Source.local(BASIC)).withoutLogin().andNoPublicMessage()); - expectedException.expectMessage("Invalid basic header"); - underTest.authenticate(request); - } - - @Test - public void authenticate_from_user_token() { - UserDto user = db.users().insertUser(); - when(userTokenAuthentication.authenticate("token")).thenReturn(Optional.of(user.getUuid())); - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); - - Optional<UserDto> userAuthenticated = underTest.authenticate(request); - - assertThat(userAuthenticated.isPresent()).isTrue(); - assertThat(userAuthenticated.get().getLogin()).isEqualTo(user.getLogin()); - verify(authenticationEvent).loginSuccess(request, user.getLogin(), Source.local(BASIC_TOKEN)); - } - - @Test - public void does_not_authenticate_from_user_token_when_token_is_invalid() { - when(userTokenAuthentication.authenticate("token")).thenReturn(Optional.empty()); - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); - - expectedException.expect(authenticationException().from(Source.local(BASIC_TOKEN)).withoutLogin().andNoPublicMessage()); - try { - underTest.authenticate(request); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - @Test - public void does_not_authenticate_from_user_token_when_token_does_not_match_existing_user() { - when(userTokenAuthentication.authenticate("token")).thenReturn(Optional.of("Unknown user")); - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); - - expectedException.expect(authenticationException().from(Source.local(Method.BASIC_TOKEN)).withoutLogin().andNoPublicMessage()); - try { - underTest.authenticate(request); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - @Test - public void does_not_authenticate_from_user_token_when_token_does_not_match_active_user() { - UserDto user = db.users().insertDisabledUser(); - when(userTokenAuthentication.authenticate("token")).thenReturn(Optional.of(user.getUuid())); - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); - - expectedException.expect(authenticationException().from(Source.local(Method.BASIC_TOKEN)).withoutLogin().andNoPublicMessage()); - try { - underTest.authenticate(request); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - private static String toBase64(String text) { - return new String(BASE64_ENCODER.encode(text.getBytes(UTF_8))); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/CookiesTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/CookiesTest.java deleted file mode 100644 index d7b47679da6..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/CookiesTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.server.authentication.Cookies.findCookie; -import static org.sonar.server.authentication.Cookies.newCookieBuilder; - -public class CookiesTest { - - private static final String HTTPS_HEADER = "X-Forwarded-Proto"; - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private HttpServletRequest request = mock(HttpServletRequest.class); - - @Test - public void create_cookie() { - Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").setHttpOnly(true).setExpiry(10).build(); - assertThat(cookie.getName()).isEqualTo("name"); - assertThat(cookie.getValue()).isEqualTo("value"); - assertThat(cookie.isHttpOnly()).isTrue(); - assertThat(cookie.getMaxAge()).isEqualTo(10); - assertThat(cookie.getSecure()).isFalse(); - assertThat(cookie.getPath()).isEqualTo("/"); - } - - @Test - public void create_cookie_without_value() { - Cookie cookie = newCookieBuilder(request).setName("name").build(); - assertThat(cookie.getName()).isEqualTo("name"); - assertThat(cookie.getValue()).isNull(); - } - - @Test - public void create_cookie_when_web_context() { - when(request.getContextPath()).thenReturn("/sonarqube"); - Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").setHttpOnly(true).setExpiry(10).build(); - assertThat(cookie.getName()).isEqualTo("name"); - assertThat(cookie.getValue()).isEqualTo("value"); - assertThat(cookie.isHttpOnly()).isTrue(); - assertThat(cookie.getMaxAge()).isEqualTo(10); - assertThat(cookie.getSecure()).isFalse(); - assertThat(cookie.getPath()).isEqualTo("/sonarqube"); - } - - @Test - public void create_not_secured_cookie_when_header_is_not_http() { - when(request.getHeader(HTTPS_HEADER)).thenReturn("http"); - Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").setHttpOnly(true).setExpiry(10).build(); - assertThat(cookie.getSecure()).isFalse(); - } - - @Test - public void create_secured_cookie_when_X_Forwarded_Proto_header_is_https() { - when(request.getHeader(HTTPS_HEADER)).thenReturn("https"); - Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").setHttpOnly(true).setExpiry(10).build(); - assertThat(cookie.getSecure()).isTrue(); - } - - @Test - public void create_secured_cookie_when_X_Forwarded_Proto_header_is_HTTPS() { - when(request.getHeader(HTTPS_HEADER)).thenReturn("HTTPS"); - Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").setHttpOnly(true).setExpiry(10).build(); - assertThat(cookie.getSecure()).isTrue(); - } - - @Test - public void find_cookie() { - Cookie cookie = newCookieBuilder(request).setName("name").setValue("value").build(); - when(request.getCookies()).thenReturn(new Cookie[] {cookie}); - - assertThat(findCookie("name", request)).isPresent(); - assertThat(findCookie("NAME", request)).isEmpty(); - assertThat(findCookie("unknown", request)).isEmpty(); - } - - @Test - public void does_not_fail_to_find_cookie_when_no_cookie() { - assertThat(findCookie("unknown", request)).isEmpty(); - } - - @Test - public void fail_with_NPE_when_cookie_name_is_null() { - expectedException.expect(NullPointerException.class); - newCookieBuilder(request).setName(null); - } - - @Test - public void fail_with_NPE_when_cookie_has_no_name() { - expectedException.expect(NullPointerException.class); - newCookieBuilder(request).setName(null); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticationTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticationTest.java deleted file mode 100644 index 315d5e83f1d..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsAuthenticationTest.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.event.AuthenticationEvent; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.rules.ExpectedException.none; -import static org.mockito.Mockito.mock; -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; -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; - -public class CredentialsAuthenticationTest { - - private static final String LOGIN = "LOGIN"; - private static final String PASSWORD = "PASSWORD"; - private static final String SALT = "0242b0b4c0a93ddfe09dd886de50bc25ba000b51"; - private static final String ENCRYPTED_PASSWORD = "540e4fc4be4e047db995bc76d18374a5b5db08cc"; - - @Rule - public ExpectedException expectedException = none(); - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - private DbClient dbClient = dbTester.getDbClient(); - private DbSession dbSession = dbTester.getSession(); - private HttpServletRequest request = mock(HttpServletRequest.class); - private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - private CredentialsExternalAuthentication externalAuthentication = mock(CredentialsExternalAuthentication.class); - private CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(dbClient); - private CredentialsAuthentication underTest = new CredentialsAuthentication(dbClient, authenticationEvent, externalAuthentication, localAuthentication); - - @Test - public void authenticate_local_user() { - insertUser(newUserDto() - .setLogin(LOGIN) - .setCryptedPassword(ENCRYPTED_PASSWORD) - .setHashMethod(CredentialsLocalAuthentication.HashMethod.SHA1.name()) - .setSalt(SALT) - .setLocal(true)); - - UserDto userDto = executeAuthenticate(BASIC); - assertThat(userDto.getLogin()).isEqualTo(LOGIN); - verify(authenticationEvent).loginSuccess(request, LOGIN, Source.local(BASIC)); - } - - @Test - public void fail_to_authenticate_local_user_when_password_is_wrong() { - insertUser(newUserDto() - .setLogin(LOGIN) - .setCryptedPassword("Wrong password") - .setSalt("Wrong salt") - .setHashMethod(CredentialsLocalAuthentication.HashMethod.SHA1.name()) - .setLocal(true)); - - expectedException.expect(authenticationException().from(Source.local(BASIC)).withLogin(LOGIN).andNoPublicMessage()); - expectedException.expectMessage("wrong password"); - try { - executeAuthenticate(BASIC); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - @Test - public void authenticate_external_user() { - when(externalAuthentication.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC)).thenReturn(Optional.of(newUserDto())); - insertUser(newUserDto() - .setLogin(LOGIN) - .setLocal(false)); - - executeAuthenticate(BASIC); - - verify(externalAuthentication).authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void fail_to_authenticate_authenticate_external_user_when_no_external_authentication() { - when(externalAuthentication.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC_TOKEN)).thenReturn(Optional.empty()); - insertUser(newUserDto() - .setLogin(LOGIN) - .setLocal(false)); - - expectedException.expect(authenticationException().from(Source.local(BASIC_TOKEN)).withLogin(LOGIN).andNoPublicMessage()); - expectedException.expectMessage("User is not local"); - try { - executeAuthenticate(BASIC_TOKEN); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - @Test - public void fail_to_authenticate_local_user_that_have_no_password() { - insertUser(newUserDto() - .setLogin(LOGIN) - .setCryptedPassword(null) - .setSalt(SALT) - .setHashMethod(CredentialsLocalAuthentication.HashMethod.SHA1.name()) - .setLocal(true)); - - expectedException.expect(authenticationException().from(Source.local(BASIC)).withLogin(LOGIN).andNoPublicMessage()); - expectedException.expectMessage("null password in DB"); - try { - executeAuthenticate(BASIC); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - @Test - public void fail_to_authenticate_local_user_that_have_no_salt() { - insertUser(newUserDto() - .setLogin(LOGIN) - .setCryptedPassword(ENCRYPTED_PASSWORD) - .setSalt(null) - .setHashMethod(CredentialsLocalAuthentication.HashMethod.SHA1.name()) - .setLocal(true)); - - expectedException.expect(authenticationException().from(Source.local(BASIC_TOKEN)).withLogin(LOGIN).andNoPublicMessage()); - expectedException.expectMessage("null salt"); - try { - executeAuthenticate(BASIC_TOKEN); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - private UserDto executeAuthenticate(AuthenticationEvent.Method method) { - return underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, method); - } - - private UserDto insertUser(UserDto userDto) { - dbClient.userDao().insert(dbSession, userDto); - dbSession.commit(); - return userDto; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsExternalAuthenticationTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsExternalAuthenticationTest.java deleted file mode 100644 index ee90eb0d094..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsExternalAuthenticationTest.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.http.HttpServletRequest; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.security.Authenticator; -import org.sonar.api.security.ExternalGroupsProvider; -import org.sonar.api.security.ExternalUsersProvider; -import org.sonar.api.security.SecurityRealm; -import org.sonar.api.security.UserDetails; -import org.sonar.server.authentication.event.AuthenticationEvent; -import org.sonar.server.authentication.event.AuthenticationEvent.Source; -import org.sonar.server.user.SecurityRealmFactory; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.rules.ExpectedException.none; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -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; - -public class CredentialsExternalAuthenticationTest { - - private static final String LOGIN = "LOGIN"; - private static final String PASSWORD = "PASSWORD"; - - private static final String REALM_NAME = "realm name"; - - @Rule - public ExpectedException expectedException = none(); - - private MapSettings settings = new MapSettings(); - - private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class); - private SecurityRealm realm = mock(SecurityRealm.class); - private Authenticator authenticator = mock(Authenticator.class); - private ExternalUsersProvider externalUsersProvider = mock(ExternalUsersProvider.class); - private ExternalGroupsProvider externalGroupsProvider = mock(ExternalGroupsProvider.class); - - private TestUserRegistrar userIdentityAuthenticator = new TestUserRegistrar(); - private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - - private HttpServletRequest request = mock(HttpServletRequest.class); - - private CredentialsExternalAuthentication underTest = new CredentialsExternalAuthentication(settings.asConfig(), securityRealmFactory, userIdentityAuthenticator, authenticationEvent); - - @Before - public void setUp() throws Exception { - when(realm.getName()).thenReturn(REALM_NAME); - } - - @Test - public void authenticate() { - executeStartWithoutGroupSync(); - when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true); - UserDetails userDetails = new UserDetails(); - userDetails.setName("name"); - userDetails.setEmail("email"); - when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails); - - underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC); - - assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue(); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getExistingEmailStrategy()).isEqualTo(FORBID); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getLogin()).isEqualTo(LOGIN); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo(LOGIN); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderId()).isNull(); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo("name"); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getEmail()).isEqualTo("email"); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().shouldSyncGroups()).isFalse(); - verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME)); - } - - @Test - public void authenticate_with_sonarqube_identity_provider() { - executeStartWithoutGroupSync(); - when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true); - UserDetails userDetails = new UserDetails(); - userDetails.setName("name"); - userDetails.setEmail("email"); - when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails); - - underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC); - - assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue(); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getKey()).isEqualTo("sonarqube"); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getName()).isEqualTo("sonarqube"); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getDisplay()).isNull(); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().isEnabled()).isTrue(); - verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME)); - } - - @Test - public void login_is_used_when_no_name_provided() { - executeStartWithoutGroupSync(); - when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true); - UserDetails userDetails = new UserDetails(); - userDetails.setEmail("email"); - when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails); - - underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC); - - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getName()).isEqualTo("sonarqube"); - verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME)); - } - - @Test - public void authenticate_with_group_sync() { - when(externalGroupsProvider.doGetGroups(any(ExternalGroupsProvider.Context.class))).thenReturn(asList("group1", "group2")); - executeStartWithGroupSync(); - - executeAuthenticate(); - - assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue(); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().shouldSyncGroups()).isTrue(); - verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME)); - } - - @Test - public void use_login_if_user_details_contains_no_name() { - executeStartWithoutGroupSync(); - when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true); - UserDetails userDetails = new UserDetails(); - userDetails.setName(null); - when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails); - - underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC); - - assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue(); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo(LOGIN); - verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME)); - } - - @Test - public void use_downcase_login() { - settings.setProperty("sonar.authenticator.downcase", true); - executeStartWithoutGroupSync(); - - executeAuthenticate("LOGIN"); - - assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue(); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getLogin()).isEqualTo("login"); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("login"); - verify(authenticationEvent).loginSuccess(request, "login", Source.realm(BASIC, REALM_NAME)); - } - - @Test - public void does_not_user_downcase_login() { - settings.setProperty("sonar.authenticator.downcase", false); - executeStartWithoutGroupSync(); - - executeAuthenticate("LoGiN"); - - assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue(); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getLogin()).isEqualTo("LoGiN"); - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("LoGiN"); - verify(authenticationEvent).loginSuccess(request, "LoGiN", Source.realm(BASIC, REALM_NAME)); - } - - @Test - public void fail_to_authenticate_when_user_details_are_null() { - executeStartWithoutGroupSync(); - when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true); - - when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(null); - - expectedException.expect(authenticationException().from(Source.realm(BASIC, REALM_NAME)).withLogin(LOGIN).andNoPublicMessage()); - expectedException.expectMessage("No user details"); - try { - underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - @Test - public void fail_to_authenticate_when_external_authentication_fails() { - executeStartWithoutGroupSync(); - when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(new UserDetails()); - - when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(false); - - expectedException.expect(authenticationException().from(Source.realm(BASIC, REALM_NAME)).withLogin(LOGIN).andNoPublicMessage()); - expectedException.expectMessage("Realm returned authenticate=false"); - try { - underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - @Test - public void fail_to_authenticate_when_any_exception_is_thrown() { - executeStartWithoutGroupSync(); - String expectedMessage = "emulating exception in doAuthenticate"; - doThrow(new IllegalArgumentException(expectedMessage)).when(authenticator).doAuthenticate(any(Authenticator.Context.class)); - - when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(new UserDetails()); - - expectedException.expect(authenticationException().from(Source.realm(BASIC_TOKEN, REALM_NAME)).withLogin(LOGIN).andNoPublicMessage()); - expectedException.expectMessage(expectedMessage); - try { - underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC_TOKEN); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - @Test - public void return_empty_user_when_no_realm() { - assertThat(underTest.authenticate(new Credentials(LOGIN, PASSWORD), request, BASIC)).isEmpty(); - verifyNoMoreInteractions(authenticationEvent); - } - - @Test - public void fail_to_start_when_no_authenticator() { - when(realm.doGetAuthenticator()).thenReturn(null); - when(securityRealmFactory.getRealm()).thenReturn(realm); - - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("No authenticator available"); - underTest.start(); - } - - @Test - public void fail_to_start_when_no_user_provider() { - when(realm.doGetAuthenticator()).thenReturn(authenticator); - when(realm.getUsersProvider()).thenReturn(null); - when(securityRealmFactory.getRealm()).thenReturn(realm); - - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("No users provider available"); - underTest.start(); - } - - private void executeStartWithoutGroupSync() { - when(realm.doGetAuthenticator()).thenReturn(authenticator); - when(realm.getUsersProvider()).thenReturn(externalUsersProvider); - when(securityRealmFactory.getRealm()).thenReturn(realm); - underTest.start(); - } - - private void executeStartWithGroupSync() { - when(realm.doGetAuthenticator()).thenReturn(authenticator); - when(realm.getUsersProvider()).thenReturn(externalUsersProvider); - when(realm.getGroupsProvider()).thenReturn(externalGroupsProvider); - when(securityRealmFactory.getRealm()).thenReturn(realm); - underTest.start(); - } - - private void executeAuthenticate() { - executeAuthenticate(LOGIN); - } - - private void executeAuthenticate(String login) { - when(authenticator.doAuthenticate(any(Authenticator.Context.class))).thenReturn(true); - UserDetails userDetails = new UserDetails(); - userDetails.setName("name"); - when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails); - underTest.authenticate(new Credentials(login, PASSWORD), request, BASIC); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsLocalAuthenticationTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsLocalAuthenticationTest.java deleted file mode 100644 index 1f7078db322..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsLocalAuthenticationTest.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Optional; -import java.util.Random; -import org.apache.commons.codec.digest.DigestUtils; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mindrot.jbcrypt.BCrypt; -import org.sonar.db.DbTester; -import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.event.AuthenticationEvent; -import org.sonar.server.authentication.event.AuthenticationException; - -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.db.user.UserTesting.newUserDto; -import static org.sonar.server.authentication.CredentialsLocalAuthentication.HashMethod.BCRYPT; -import static org.sonar.server.authentication.CredentialsLocalAuthentication.HashMethod.SHA1; - -public class CredentialsLocalAuthenticationTest { - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Rule - public DbTester db = DbTester.create(); - - private static final Random RANDOM = new Random(); - - private CredentialsLocalAuthentication underTest = new CredentialsLocalAuthentication(db.getDbClient()); - - @Test - public void incorrect_hash_should_throw_AuthenticationException() { - UserDto user = newUserDto() - .setHashMethod("ALGON2"); - - expectedException.expect(AuthenticationException.class); - expectedException.expectMessage("Unknown hash method [ALGON2]"); - - underTest.authenticate(db.getSession(), user, "whatever", AuthenticationEvent.Method.BASIC); - } - - @Test - public void null_hash_should_throw_AuthenticationException() { - UserDto user = newUserDto(); - - expectedException.expect(AuthenticationException.class); - expectedException.expectMessage("null hash method"); - - underTest.authenticate(db.getSession(), user, "whatever", AuthenticationEvent.Method.BASIC); - } - - @Test - public void authentication_with_bcrypt_with_correct_password_should_work() { - String password = randomAlphanumeric(60); - - UserDto user = newUserDto() - .setHashMethod(BCRYPT.name()) - .setCryptedPassword(BCrypt.hashpw(password, BCrypt.gensalt(12))); - - underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC); - } - - @Test - public void authentication_with_sha1_with_correct_password_should_work() { - String password = randomAlphanumeric(60); - - byte[] saltRandom = new byte[20]; - RANDOM.nextBytes(saltRandom); - String salt = DigestUtils.sha1Hex(saltRandom); - - UserDto user = newUserDto() - .setHashMethod(SHA1.name()) - .setCryptedPassword(DigestUtils.sha1Hex("--" + salt + "--" + password + "--")) - .setSalt(salt); - - underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC); - } - - @Test - public void authentication_with_sha1_with_incorrect_password_should_throw_AuthenticationException() { - String password = randomAlphanumeric(60); - - byte[] saltRandom = new byte[20]; - RANDOM.nextBytes(saltRandom); - String salt = DigestUtils.sha1Hex(saltRandom); - - UserDto user = newUserDto() - .setHashMethod(SHA1.name()) - .setCryptedPassword(DigestUtils.sha1Hex("--" + salt + "--" + password + "--")) - .setSalt(salt); - - expectedException.expect(AuthenticationException.class); - expectedException.expectMessage("wrong password"); - - underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC); - } - - @Test - public void authentication_with_sha1_with_empty_password_should_throw_AuthenticationException() { - byte[] saltRandom = new byte[20]; - RANDOM.nextBytes(saltRandom); - String salt = DigestUtils.sha1Hex(saltRandom); - - UserDto user = newUserDto() - .setCryptedPassword(null) - .setHashMethod(SHA1.name()) - .setSalt(salt); - - expectedException.expect(AuthenticationException.class); - expectedException.expectMessage("null password in DB"); - - underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC); - } - - @Test - public void authentication_with_sha1_with_empty_salt_should_throw_AuthenticationException() { - String password = randomAlphanumeric(60); - - UserDto user = newUserDto() - .setHashMethod(SHA1.name()) - .setCryptedPassword(DigestUtils.sha1Hex("--0242b0b4c0a93ddfe09dd886de50bc25ba000b51--" + password + "--")) - .setSalt(null); - - expectedException.expect(AuthenticationException.class); - expectedException.expectMessage("null salt"); - - underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC); - } - - @Test - public void authentication_with_bcrypt_with_incorrect_password_should_throw_AuthenticationException() { - String password = randomAlphanumeric(60); - - UserDto user = newUserDto() - .setHashMethod(BCRYPT.name()) - .setCryptedPassword(BCrypt.hashpw(password, BCrypt.gensalt(12))); - - expectedException.expect(AuthenticationException.class); - expectedException.expectMessage("wrong password"); - - underTest.authenticate(db.getSession(), user, "WHATEVER", AuthenticationEvent.Method.BASIC); - } - - @Test - public void authentication_upgrade_hash_function_when_SHA1_was_used() { - String password = randomAlphanumeric(60); - - byte[] saltRandom = new byte[20]; - RANDOM.nextBytes(saltRandom); - String salt = DigestUtils.sha1Hex(saltRandom); - - UserDto user = newUserDto() - .setLogin("myself") - .setHashMethod(SHA1.name()) - .setCryptedPassword(DigestUtils.sha1Hex("--" + salt + "--" + password + "--")) - .setSalt(salt); - db.users().insertUser(user); - - underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC); - - Optional<UserDto> myself = db.users().selectUserByLogin("myself"); - assertThat(myself).isPresent(); - assertThat(myself.get().getHashMethod()).isEqualTo(BCRYPT.name()); - assertThat(myself.get().getSalt()).isNull(); - - // authentication must work with upgraded hash method - underTest.authenticate(db.getSession(), user, password, AuthenticationEvent.Method.BASIC); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsTest.java deleted file mode 100644 index f767b0fa4a1..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/CredentialsTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; - -public class CredentialsTest { - - @Test - public void login_cant_be_empty() { - Throwable thrown = catchThrowable(() -> new Credentials("", "bar")); - assertThat(thrown) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("login must not be null nor empty"); - - thrown = catchThrowable(() -> new Credentials(null, "bar")); - assertThat(thrown) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("login must not be null nor empty"); - - Credentials underTest = new Credentials("foo", "bar"); - assertThat(underTest.getLogin()).isEqualTo("foo"); - } - - @Test - public void password_cant_be_empty_string() { - Credentials underTest = new Credentials("foo", ""); - assertThat(underTest.getPassword()).isEmpty(); - - underTest = new Credentials("foo", null); - assertThat(underTest.getPassword()).isEmpty(); - - underTest = new Credentials("foo", " "); - assertThat(underTest.getPassword()).hasValue(" "); - - underTest = new Credentials("foo", "bar"); - assertThat(underTest.getPassword()).hasValue("bar"); - } - - @Test - public void test_equality() { - assertThat(new Credentials("foo", "bar")).isEqualTo(new Credentials("foo", "bar")); - assertThat(new Credentials("foo", "bar")).isNotEqualTo(new Credentials("foo", "baaaar")); - assertThat(new Credentials("foo", "bar")).isNotEqualTo(new Credentials("foooooo", "bar")); - assertThat(new Credentials("foo", "bar")).isNotEqualTo(new Credentials("foo", null)); - - assertThat(new Credentials("foo", "bar").hashCode()).isEqualTo(new Credentials("foo", "bar").hashCode()); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeBasicIdentityProvider.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeBasicIdentityProvider.java deleted file mode 100644 index 22e794abc93..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeBasicIdentityProvider.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.sonar.api.server.authentication.BaseIdentityProvider; - -class FakeBasicIdentityProvider extends TestIdentityProvider implements BaseIdentityProvider { - - private boolean initCalled = false; - - public FakeBasicIdentityProvider(String key, boolean enabled) { - setKey(key); - setName("name of " + key); - setEnabled(enabled); - } - - @Override - public void init(Context context) { - initCalled = true; - } - - public boolean isInitCalled() { - return initCalled; - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeOAuth2IdentityProvider.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeOAuth2IdentityProvider.java deleted file mode 100644 index ae50241c89a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/FakeOAuth2IdentityProvider.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.sonar.api.server.authentication.OAuth2IdentityProvider; - -class FakeOAuth2IdentityProvider extends TestIdentityProvider implements OAuth2IdentityProvider { - - private boolean initCalled = false; - private boolean callbackCalled = false; - - public FakeOAuth2IdentityProvider(String key, boolean enabled) { - setKey(key); - setName("name of " + key); - setEnabled(enabled); - } - - @Override - public void init(InitContext context) { - initCalled = true; - } - - @Override - public void callback(CallbackContext context) { - callbackCalled = true; - } - - public boolean isInitCalled() { - return initCalled; - } - - public boolean isCallbackCalled() { - return callbackCalled; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/HttpHeadersAuthenticationTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/HttpHeadersAuthenticationTest.java deleted file mode 100644 index 0a1672b9dcc..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/HttpHeadersAuthenticationTest.java +++ /dev/null @@ -1,457 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.collect.ImmutableMap; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; -import org.sonar.api.utils.System2; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbTester; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.event.AuthenticationEvent; -import org.sonar.server.authentication.event.AuthenticationEvent.Source; -import org.sonar.server.es.EsTester; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.OrganizationUpdater; -import org.sonar.server.organization.TestDefaultOrganizationProvider; -import org.sonar.server.organization.TestOrganizationFlags; -import org.sonar.server.user.NewUserNotifier; -import org.sonar.server.user.UserUpdater; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static java.util.Arrays.stream; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.rules.ExpectedException.none; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyMap; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -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.AuthenticationExceptionMatcher.authenticationException; - -public class HttpHeadersAuthenticationTest { - - private MapSettings settings = new MapSettings(); - - @Rule - public ExpectedException expectedException = none(); - @Rule - public DbTester db = DbTester.create(new AlwaysIncreasingSystem2()); - @Rule - public EsTester es = EsTester.create(); - - private static final String DEFAULT_LOGIN = "john"; - private static final String DEFAULT_NAME = "John"; - private static final String DEFAULT_EMAIL = "john@doo.com"; - private static final String GROUP1 = "dev"; - private static final String GROUP2 = "admin"; - private static final String GROUPS = GROUP1 + "," + GROUP2; - - private static final Long NOW = 1_000_000L; - private static final Long CLOSE_REFRESH_TIME = NOW - 1_000L; - - private static final UserDto DEFAULT_USER = newUserDto() - .setLogin(DEFAULT_LOGIN) - .setName(DEFAULT_NAME) - .setEmail(DEFAULT_EMAIL) - .setExternalLogin(DEFAULT_LOGIN) - .setExternalIdentityProvider("sonarqube"); - - private GroupDto group1; - private GroupDto group2; - private GroupDto sonarUsers; - - private System2 system2 = mock(System2.class); - private OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.class); - private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); - private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); - private CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient()); - - private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client()); - private UserRegistrarImpl userIdentityAuthenticator = new UserRegistrarImpl( - db.getDbClient(), - new UserUpdater(system2, mock(NewUserNotifier.class), db.getDbClient(), userIndexer, organizationFlags, defaultOrganizationProvider, - new DefaultGroupFinder(db.getDbClient()), settings.asConfig(), localAuthentication), - defaultOrganizationProvider, organizationFlags, new DefaultGroupFinder(db.getDbClient()), null); - - private HttpServletResponse response = mock(HttpServletResponse.class); - private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); - private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - - private HttpHeadersAuthentication underTest = new HttpHeadersAuthentication(system2, settings.asConfig(), userIdentityAuthenticator, jwtHttpHandler, authenticationEvent); - - @Before - public void setUp() throws Exception { - when(system2.now()).thenReturn(NOW); - group1 = db.users().insertGroup(db.getDefaultOrganization(), GROUP1); - group2 = db.users().insertGroup(db.getDefaultOrganization(), GROUP2); - sonarUsers = db.users().insertDefaultGroup(db.getDefaultOrganization(), "sonar-users"); - } - - @Test - public void create_user_when_authenticating_new_user() { - startWithSso(); - setNotUserInToken(); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUPS); - - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2, sonarUsers); - verifyTokenIsUpdated(NOW); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void use_login_when_name_is_not_provided() { - startWithSso(); - setNotUserInToken(); - - HttpServletRequest request = createRequest(DEFAULT_LOGIN, null, null, null); - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_LOGIN, null, sonarUsers); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void update_user_when_authenticating_exiting_user() { - startWithSso(); - setNotUserInToken(); - insertUser(newUserDto().setLogin(DEFAULT_LOGIN).setName("old name").setEmail("old email"), group1); - // Name, email and groups are different - HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUP2); - - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group2); - verifyTokenIsUpdated(NOW); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void remove_groups_when_group_headers_is_empty() { - startWithSso(); - setNotUserInToken(); - insertUser(DEFAULT_USER, group1); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, ""); - - underTest.authenticate(request, response); - - verityUserHasNoGroup(DEFAULT_LOGIN); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void remove_groups_when_group_headers_is_null() { - startWithSso(); - setNotUserInToken(); - insertUser(DEFAULT_USER, group1); - Map<String, String> headerValuesByName = new HashMap<>(); - headerValuesByName.put("X-Forwarded-Login", DEFAULT_LOGIN); - headerValuesByName.put("X-Forwarded-Groups", null); - HttpServletRequest request = createRequest(headerValuesByName); - - underTest.authenticate(request, response); - - verityUserHasNoGroup(DEFAULT_LOGIN); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void does_not_update_groups_when_no_group_headers() { - startWithSso(); - setNotUserInToken(); - insertUser(DEFAULT_USER, group1, sonarUsers); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, null); - - underTest.authenticate(request, response); - - verityUserGroups(DEFAULT_LOGIN, group1, sonarUsers); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void does_not_update_user_when_user_is_in_token_and_refresh_time_is_close() { - startWithSso(); - UserDto user = insertUser(DEFAULT_USER, group1); - setUserInToken(user, CLOSE_REFRESH_TIME); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); - - underTest.authenticate(request, response); - - // User is not updated - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1); - verifyTokenIsNotUpdated(); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void update_user_when_user_in_token_but_refresh_time_is_old() { - startWithSso(); - UserDto user = insertUser(DEFAULT_USER, group1); - // Refresh time was updated 6 minutes ago => more than 5 minutes - setUserInToken(user, NOW - 6 * 60 * 1000L); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); - - underTest.authenticate(request, response); - - // User is updated - verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2); - verifyTokenIsUpdated(NOW); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void update_user_when_user_in_token_but_no_refresh_time() { - startWithSso(); - UserDto user = insertUser(DEFAULT_USER, group1); - setUserInToken(user, null); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); - - underTest.authenticate(request, response); - - // User is updated - verifyUserInDb(DEFAULT_LOGIN, "new name", "new email", group2); - verifyTokenIsUpdated(NOW); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void use_refresh_time_from_settings() { - settings.setProperty("sonar.web.sso.refreshIntervalInMinutes", "10"); - startWithSso(); - UserDto user = insertUser(DEFAULT_USER, group1); - // Refresh time was updated 6 minutes ago => less than 10 minutes ago so not updated - setUserInToken(user, NOW - 6 * 60 * 1000L); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, "new name", "new email", GROUP2); - - underTest.authenticate(request, response); - - // User is not updated - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1); - verifyTokenIsNotUpdated(); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void update_user_when_login_from_token_is_different_than_login_from_request() { - startWithSso(); - insertUser(DEFAULT_USER, group1); - setUserInToken(DEFAULT_USER, CLOSE_REFRESH_TIME); - HttpServletRequest request = createRequest("AnotherLogin", "Another name", "Another email", GROUP2); - - underTest.authenticate(request, response); - - verifyUserInDb("AnotherLogin", "Another name", "Another email", group2, sonarUsers); - verifyTokenIsUpdated(NOW); - verify(authenticationEvent).loginSuccess(request, "AnotherLogin", Source.sso()); - } - - @Test - public void use_headers_from_settings() { - settings.setProperty("sonar.web.sso.loginHeader", "head-login"); - settings.setProperty("sonar.web.sso.nameHeader", "head-name"); - settings.setProperty("sonar.web.sso.emailHeader", "head-email"); - settings.setProperty("sonar.web.sso.groupsHeader", "head-groups"); - startWithSso(); - setNotUserInToken(); - HttpServletRequest request = createRequest(ImmutableMap.of("head-login", DEFAULT_LOGIN, "head-name", DEFAULT_NAME, "head-email", DEFAULT_EMAIL, "head-groups", GROUPS)); - - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2, sonarUsers); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void detect_group_header_even_with_wrong_case() { - settings.setProperty("sonar.web.sso.loginHeader", "login"); - settings.setProperty("sonar.web.sso.nameHeader", "name"); - settings.setProperty("sonar.web.sso.emailHeader", "email"); - settings.setProperty("sonar.web.sso.groupsHeader", "Groups"); - startWithSso(); - setNotUserInToken(); - HttpServletRequest request = createRequest(ImmutableMap.of("login", DEFAULT_LOGIN, "name", DEFAULT_NAME, "email", DEFAULT_EMAIL, "groups", GROUPS)); - - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, group1, group2, sonarUsers); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void trim_groups() { - startWithSso(); - setNotUserInToken(); - HttpServletRequest request = createRequest(DEFAULT_LOGIN, null, null, " dev , admin "); - - underTest.authenticate(request, response); - - verifyUserInDb(DEFAULT_LOGIN, DEFAULT_LOGIN, null, group1, group2, sonarUsers); - verify(authenticationEvent).loginSuccess(request, DEFAULT_LOGIN, Source.sso()); - } - - @Test - public void does_not_authenticate_when_no_header() { - startWithSso(); - setNotUserInToken(); - - underTest.authenticate(createRequest(Collections.emptyMap()), response); - - verifyUserNotAuthenticated(); - verifyTokenIsNotUpdated(); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void does_not_authenticate_when_not_enabled() { - startWithoutSso(); - - underTest.authenticate(createRequest(DEFAULT_LOGIN, DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response); - - verifyUserNotAuthenticated(); - verifyZeroInteractions(jwtHttpHandler, authenticationEvent); - } - - @Test - public void throw_AuthenticationException_when_BadRequestException_is_generated() { - startWithSso(); - setNotUserInToken(); - - expectedException.expect(authenticationException().from(Source.sso()).withoutLogin().andNoPublicMessage()); - expectedException.expectMessage("Use only letters, numbers, and .-_@ please."); - try { - underTest.authenticate(createRequest("invalid login", DEFAULT_NAME, DEFAULT_EMAIL, GROUPS), response); - } finally { - verifyZeroInteractions(authenticationEvent); - } - } - - private void startWithSso() { - settings.setProperty("sonar.web.sso.enable", true); - underTest.start(); - } - - private void startWithoutSso() { - settings.setProperty("sonar.web.sso.enable", false); - underTest.start(); - } - - private void setUserInToken(UserDto user, @Nullable Long lastRefreshTime) { - when(jwtHttpHandler.getToken(any(HttpServletRequest.class), any(HttpServletResponse.class))) - .thenReturn(Optional.of(new JwtHttpHandler.Token( - user, - lastRefreshTime == null ? Collections.emptyMap() : ImmutableMap.of("ssoLastRefreshTime", lastRefreshTime)))); - } - - private void setNotUserInToken() { - when(jwtHttpHandler.getToken(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(Optional.empty()); - } - - private UserDto insertUser(UserDto user, GroupDto... groups) { - db.users().insertUser(user); - stream(groups).forEach(group -> db.users().insertMember(group, user)); - db.commit(); - return user; - } - - private static HttpServletRequest createRequest(Map<String, String> headerValuesByName) { - HttpServletRequest request = mock(HttpServletRequest.class); - setHeaders(request, headerValuesByName); - return request; - } - - private static HttpServletRequest createRequest(String login, @Nullable String name, @Nullable String email, @Nullable String groups) { - Map<String, String> headerValuesByName = new HashMap<>(); - headerValuesByName.put("X-Forwarded-Login", login); - if (name != null) { - headerValuesByName.put("X-Forwarded-Name", name); - } - if (email != null) { - headerValuesByName.put("X-Forwarded-Email", email); - } - if (groups != null) { - headerValuesByName.put("X-Forwarded-Groups", groups); - } - HttpServletRequest request = mock(HttpServletRequest.class); - setHeaders(request, headerValuesByName); - return request; - } - - private static void setHeaders(HttpServletRequest request, Map<String, String> valuesByName) { - valuesByName.entrySet().forEach(entry -> when(request.getHeader(entry.getKey())).thenReturn(entry.getValue())); - when(request.getHeaderNames()).thenReturn(Collections.enumeration(valuesByName.keySet())); - } - - private void verifyUserInDb(String expectedLogin, String expectedName, @Nullable String expectedEmail, GroupDto... expectedGroups) { - UserDto userDto = db.users().selectUserByLogin(expectedLogin).get(); - assertThat(userDto.isActive()).isTrue(); - assertThat(userDto.getName()).isEqualTo(expectedName); - assertThat(userDto.getEmail()).isEqualTo(expectedEmail); - assertThat(userDto.getExternalLogin()).isEqualTo(expectedLogin); - assertThat(userDto.getExternalIdentityProvider()).isEqualTo("sonarqube"); - verityUserGroups(expectedLogin, expectedGroups); - } - - private void verityUserGroups(String login, GroupDto... expectedGroups) { - UserDto userDto = db.users().selectUserByLogin(login).get(); - if (expectedGroups.length == 0) { - assertThat(db.users().selectGroupIdsOfUser(userDto)).isEmpty(); - } else { - assertThat(db.users().selectGroupIdsOfUser(userDto)).containsOnly(stream(expectedGroups).map(GroupDto::getId).collect(MoreCollectors.toList()).toArray(new Integer[] {})); - } - } - - private void verityUserHasNoGroup(String login) { - verityUserGroups(login); - } - - private void verifyUserNotAuthenticated() { - assertThat(db.countRowsOfTable(db.getSession(), "users")).isZero(); - verifyTokenIsNotUpdated(); - } - - private void verifyTokenIsUpdated(long refreshTime) { - verify(jwtHttpHandler).generateToken(any(UserDto.class), eq(ImmutableMap.of("ssoLastRefreshTime", refreshTime)), any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - private void verifyTokenIsNotUpdated() { - verify(jwtHttpHandler, never()).generateToken(any(UserDto.class), anyMap(), any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryRule.java deleted file mode 100644 index 533c90934b3..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryRule.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.sonar.api.server.authentication.IdentityProvider; - -public class IdentityProviderRepositoryRule extends IdentityProviderRepository implements TestRule { - - public IdentityProviderRepositoryRule addIdentityProvider(IdentityProvider identityProvider) { - providersByKey.put(identityProvider.getKey(), identityProvider); - return this; - } - - @Override - public Statement apply(final Statement statement, Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - try { - statement.evaluate(); - } finally { - providersByKey.clear(); - } - } - }; - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryTest.java deleted file mode 100644 index 306ab69b70a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.List; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.server.authentication.IdentityProvider; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; - -public class IdentityProviderRepositoryTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - static IdentityProvider GITHUB = new TestIdentityProvider() - .setKey("github") - .setName("Github") - .setEnabled(true); - - static IdentityProvider BITBUCKET = new TestIdentityProvider() - .setKey("bitbucket") - .setName("Bitbucket") - .setEnabled(true); - - static IdentityProvider DISABLED = new TestIdentityProvider() - .setKey("disabled") - .setName("Disabled") - .setEnabled(false); - - @Test - public void return_enabled_provider() { - IdentityProviderRepository underTest = new IdentityProviderRepository(asList(GITHUB, BITBUCKET, DISABLED)); - - assertThat(underTest.getEnabledByKey(GITHUB.getKey())).isEqualTo(GITHUB); - assertThat(underTest.getEnabledByKey(BITBUCKET.getKey())).isEqualTo(BITBUCKET); - } - - @Test - public void fail_on_disabled_provider() { - IdentityProviderRepository underTest = new IdentityProviderRepository(asList(GITHUB, BITBUCKET, DISABLED)); - - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Identity provider disabled does not exist or is not enabled"); - underTest.getEnabledByKey(DISABLED.getKey()); - } - - @Test - public void return_all_enabled_providers() { - IdentityProviderRepository underTest = new IdentityProviderRepository(asList(GITHUB, BITBUCKET, DISABLED)); - - List<IdentityProvider> providers = underTest.getAllEnabledAndSorted(); - assertThat(providers).containsOnly(GITHUB, BITBUCKET); - } - - @Test - public void return_sorted_enabled_providers() { - IdentityProviderRepository underTest = new IdentityProviderRepository(asList(GITHUB, BITBUCKET)); - - List<IdentityProvider> providers = underTest.getAllEnabledAndSorted(); - assertThat(providers).containsExactly(BITBUCKET, GITHUB); - } - - @Test - public void return_nothing_when_no_identity_provider() { - IdentityProviderRepository underTest = new IdentityProviderRepository(); - - assertThat(underTest.getAllEnabledAndSorted()).isEmpty(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java deleted file mode 100644 index c23bb1ed59a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.FilterChain; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -import org.sonar.api.server.authentication.BaseIdentityProvider; -import org.sonar.api.server.authentication.Display; -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.mock; -import static org.mockito.Mockito.never; -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 { - - private static final String OAUTH2_PROVIDER_KEY = "github"; - private static final String BASIC_PROVIDER_KEY = "openid"; - - @Rule - public LogTester logTester = new LogTester(); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Rule - public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule(); - - private BaseContextFactory baseContextFactory = mock(BaseContextFactory.class); - private OAuth2ContextFactory oAuth2ContextFactory = mock(OAuth2ContextFactory.class); - - private HttpServletRequest request = mock(HttpServletRequest.class); - private HttpServletResponse response = mock(HttpServletResponse.class); - private FilterChain chain = mock(FilterChain.class); - - private FakeOAuth2IdentityProvider oAuth2IdentityProvider = new FakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true); - private OAuth2IdentityProvider.InitContext oauth2Context = mock(OAuth2IdentityProvider.InitContext.class); - - private FakeBasicIdentityProvider baseIdentityProvider = new FakeBasicIdentityProvider(BASIC_PROVIDER_KEY, true); - private BaseIdentityProvider.Context baseContext = mock(BaseIdentityProvider.Context.class); - private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - private OAuth2AuthenticationParameters auth2AuthenticationParameters = mock(OAuth2AuthenticationParameters.class); - - private ArgumentCaptor<AuthenticationException> authenticationExceptionCaptor = ArgumentCaptor.forClass(AuthenticationException.class); - private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class); - - private InitFilter underTest = new InitFilter(identityProviderRepository, baseContextFactory, oAuth2ContextFactory, authenticationEvent, auth2AuthenticationParameters); - - @Before - public void setUp() throws Exception { - when(oAuth2ContextFactory.newContext(request, response, oAuth2IdentityProvider)).thenReturn(oauth2Context); - when(baseContextFactory.newContext(request, response, baseIdentityProvider)).thenReturn(baseContext); - when(request.getContextPath()).thenReturn(""); - } - - @Test - public void do_get_pattern() { - assertThat(underTest.doGetPattern()).isNotNull(); - } - - @Test - public void do_filter_with_context() { - when(request.getContextPath()).thenReturn("/sonarqube"); - when(request.getRequestURI()).thenReturn("/sonarqube/sessions/init/" + OAUTH2_PROVIDER_KEY); - identityProviderRepository.addIdentityProvider(oAuth2IdentityProvider); - - underTest.doFilter(request, response, chain); - - assertOAuth2InitCalled(); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void do_filter_on_auth2_identity_provider() { - when(request.getRequestURI()).thenReturn("/sessions/init/" + OAUTH2_PROVIDER_KEY); - identityProviderRepository.addIdentityProvider(oAuth2IdentityProvider); - - underTest.doFilter(request, response, chain); - - assertOAuth2InitCalled(); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void do_filter_on_basic_identity_provider() { - when(request.getRequestURI()).thenReturn("/sessions/init/" + BASIC_PROVIDER_KEY); - identityProviderRepository.addIdentityProvider(baseIdentityProvider); - - underTest.doFilter(request, response, chain); - - assertBasicInitCalled(); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void init_authentication_parameter_on_auth2_identity_provider() { - when(request.getContextPath()).thenReturn("/sonarqube"); - when(request.getRequestURI()).thenReturn("/sonarqube/sessions/init/" + OAUTH2_PROVIDER_KEY); - identityProviderRepository.addIdentityProvider(oAuth2IdentityProvider); - - underTest.doFilter(request, response, chain); - - verify(auth2AuthenticationParameters).init(eq(request), eq(response)); - } - - @Test - public void does_not_init_authentication_parameter_on_basic_authentication() { - when(request.getRequestURI()).thenReturn("/sessions/init/" + BASIC_PROVIDER_KEY); - identityProviderRepository.addIdentityProvider(baseIdentityProvider); - - underTest.doFilter(request, response, chain); - - verify(auth2AuthenticationParameters, never()).init(eq(request), eq(response)); - } - - @Test - public void fail_if_identity_provider_key_is_empty() throws Exception { - when(request.getRequestURI()).thenReturn("/sessions/init/"); - - underTest.doFilter(request, response, chain); - - assertError("No provider key found in URI"); - verifyZeroInteractions(authenticationEvent); - verifyZeroInteractions(auth2AuthenticationParameters); - } - - @Test - public void fail_if_uri_does_not_contains_callback() throws Exception { - when(request.getRequestURI()).thenReturn("/sessions/init"); - - underTest.doFilter(request, response, chain); - - assertError("No provider key found in URI"); - verifyZeroInteractions(authenticationEvent); - verifyZeroInteractions(auth2AuthenticationParameters); - } - - @Test - public void fail_if_identity_provider_class_is_unsupported() throws Exception { - String unsupportedKey = "unsupported"; - when(request.getRequestURI()).thenReturn("/sessions/init/" + unsupportedKey); - IdentityProvider identityProvider = new UnsupportedIdentityProvider(unsupportedKey); - identityProviderRepository.addIdentityProvider(identityProvider); - - underTest.doFilter(request, response, chain); - - assertError("Unsupported IdentityProvider class: class org.sonar.server.authentication.InitFilterTest$UnsupportedIdentityProvider"); - verifyZeroInteractions(authenticationEvent); - verifyZeroInteractions(auth2AuthenticationParameters); - } - - @Test - public void redirect_contains_cookie_with_error_message_when_failing_because_of_UnauthorizedExceptionException() throws Exception { - IdentityProvider identityProvider = new FailWithUnauthorizedExceptionIdProvider("failing"); - when(request.getRequestURI()).thenReturn("/sessions/init/" + identityProvider.getKey()); - identityProviderRepository.addIdentityProvider(identityProvider); - - underTest.doFilter(request, response, chain); - - verify(response).sendRedirect("/sessions/unauthorized"); - verify(authenticationEvent).loginFailure(eq(request), authenticationExceptionCaptor.capture()); - AuthenticationException authenticationException = authenticationExceptionCaptor.getValue(); - assertThat(authenticationException).hasMessage("Email john@email.com is already used"); - assertThat(authenticationException.getSource()).isEqualTo(AuthenticationEvent.Source.external(identityProvider)); - assertThat(authenticationException.getLogin()).isNull(); - assertThat(authenticationException.getPublicMessage()).isEqualTo("Email john@email.com is already used"); - verifyDeleteAuthCookie(); - - verify(response).addCookie(cookieArgumentCaptor.capture()); - Cookie cookie = cookieArgumentCaptor.getValue(); - assertThat(cookie.getName()).isEqualTo("AUTHENTICATION-ERROR"); - assertThat(cookie.getValue()).isEqualTo("Email%20john%40email.com%20is%20already%20used"); - assertThat(cookie.getPath()).isEqualTo("/"); - assertThat(cookie.isHttpOnly()).isFalse(); - assertThat(cookie.getMaxAge()).isEqualTo(300); - assertThat(cookie.getSecure()).isFalse(); - } - - @Test - public void redirect_with_context_path_when_failing_because_of_UnauthorizedException() throws Exception { - when(request.getContextPath()).thenReturn("/sonarqube"); - IdentityProvider identityProvider = new FailWithUnauthorizedExceptionIdProvider("failing"); - when(request.getRequestURI()).thenReturn("/sonarqube/sessions/init/" + identityProvider.getKey()); - identityProviderRepository.addIdentityProvider(identityProvider); - - underTest.doFilter(request, response, chain); - - 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"); - when(request.getRequestURI()).thenReturn("/sessions/init/" + identityProvider.getKey()); - identityProviderRepository.addIdentityProvider(identityProvider); - - underTest.doFilter(request, response, chain); - - verify(response).sendRedirect("/sessions/unauthorized"); - assertThat(logTester.logs(LoggerLevel.WARN)).containsExactlyInAnyOrder("Fail to initialize authentication with provider 'failing'"); - verifyDeleteAuthCookie(); - } - - @Test - public void redirect_with_context_when_failing_because_of_Exception() throws Exception { - when(request.getContextPath()).thenReturn("/sonarqube"); - IdentityProvider identityProvider = new FailWithIllegalStateException("failing"); - when(request.getRequestURI()).thenReturn("/sessions/init/" + identityProvider.getKey()); - identityProviderRepository.addIdentityProvider(identityProvider); - - underTest.doFilter(request, response, chain); - - verify(response).sendRedirect("/sonarqube/sessions/unauthorized"); - } - - private void assertOAuth2InitCalled() { - assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty(); - assertThat(oAuth2IdentityProvider.isInitCalled()).isTrue(); - } - - private void assertBasicInitCalled() { - assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty(); - assertThat(baseIdentityProvider.isInitCalled()).isTrue(); - } - - private void assertError(String expectedError) throws Exception { - assertThat(logTester.logs(LoggerLevel.WARN)).contains(expectedError); - verify(response).sendRedirect("/sessions/unauthorized"); - assertThat(oAuth2IdentityProvider.isInitCalled()).isFalse(); - } - - private void verifyDeleteAuthCookie() { - verify(auth2AuthenticationParameters).delete(eq(request), eq(response)); - } - - private static class FailWithUnauthorizedExceptionIdProvider extends FakeBasicIdentityProvider { - - public FailWithUnauthorizedExceptionIdProvider(String key) { - super(key, true); - } - - @Override - public void init(Context context) { - throw new UnauthorizedException("Email john@email.com is already used"); - } - } - - private static class FailWithIllegalStateException extends FakeBasicIdentityProvider { - - public FailWithIllegalStateException(String key) { - super(key, true); - } - - @Override - public void init(Context context) { - throw new IllegalStateException("Failure !"); - } - } - - 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") - .setLogin("john.github") - .setName(existingUser.getName()) - .setEmail(existingUser.getEmail()) - .build(), this); - } - } - - private static class UnsupportedIdentityProvider implements IdentityProvider { - private final String unsupportedKey; - - public UnsupportedIdentityProvider(String unsupportedKey) { - this.unsupportedKey = unsupportedKey; - } - - @Override - public String getKey() { - return unsupportedKey; - } - - @Override - public String getName() { - return null; - } - - @Override - public Display getDisplay() { - return null; - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public boolean allowsUsersToSignUp() { - return false; - } - - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java deleted file mode 100644 index cdf97bc3b59..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.sonar.server.authentication.event.AuthenticationEvent.Method; -import static org.sonar.server.authentication.event.AuthenticationEvent.Source; -import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException; - -public class JwtCsrfVerifierTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private static final int TIMEOUT = 30; - private static final String CSRF_STATE = "STATE"; - private static final String JAVA_WS_URL = "/api/metrics/create"; - private static final String LOGIN = "foo login"; - - private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class); - - private HttpServletResponse response = mock(HttpServletResponse.class); - private HttpServletRequest request = mock(HttpServletRequest.class); - - private JwtCsrfVerifier underTest = new JwtCsrfVerifier(); - - @Before - public void setUp() throws Exception { - when(request.getContextPath()).thenReturn(""); - } - - @Test - public void generate_state() { - String state = underTest.generateState(request, response, TIMEOUT); - assertThat(state).isNotEmpty(); - - verify(response).addCookie(cookieArgumentCaptor.capture()); - verifyCookie(cookieArgumentCaptor.getValue()); - } - - @Test - public void verify_state() { - mockRequestCsrf(CSRF_STATE); - mockPostJavaWsRequest(); - - underTest.verifyState(request, CSRF_STATE, LOGIN); - } - - @Test - public void fail_with_AuthenticationException_when_state_header_is_not_the_same_as_state_parameter() { - mockRequestCsrf("other value"); - mockPostJavaWsRequest(); - - thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage()); - thrown.expectMessage("Wrong CSFR in request"); - underTest.verifyState(request, CSRF_STATE, LOGIN); - } - - @Test - public void fail_with_AuthenticationException_when_state_is_null() { - mockRequestCsrf(CSRF_STATE); - mockPostJavaWsRequest(); - - thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage()); - thrown.expectMessage("Missing reference CSRF value"); - underTest.verifyState(request, null, LOGIN); - } - - @Test - public void fail_with_AuthenticationException_when_state_parameter_is_empty() { - mockRequestCsrf(CSRF_STATE); - mockPostJavaWsRequest(); - - thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage()); - thrown.expectMessage("Missing reference CSRF value"); - underTest.verifyState(request, "", LOGIN); - } - - @Test - public void verify_POST_request() { - mockRequestCsrf("other value"); - when(request.getRequestURI()).thenReturn(JAVA_WS_URL); - when(request.getMethod()).thenReturn("POST"); - - thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage()); - thrown.expectMessage("Wrong CSFR in request"); - underTest.verifyState(request, CSRF_STATE, LOGIN); - } - - @Test - public void verify_PUT_request() { - mockRequestCsrf("other value"); - when(request.getRequestURI()).thenReturn(JAVA_WS_URL); - when(request.getMethod()).thenReturn("PUT"); - - thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage()); - thrown.expectMessage("Wrong CSFR in request"); - underTest.verifyState(request, CSRF_STATE, LOGIN); - } - - @Test - public void verify_DELETE_request() { - mockRequestCsrf("other value"); - when(request.getRequestURI()).thenReturn(JAVA_WS_URL); - when(request.getMethod()).thenReturn("DELETE"); - - thrown.expect(authenticationException().from(Source.local(Method.JWT)).withLogin(LOGIN).andNoPublicMessage()); - thrown.expectMessage("Wrong CSFR in request"); - underTest.verifyState(request, CSRF_STATE, LOGIN); - } - - @Test - public void ignore_GET_request() { - when(request.getRequestURI()).thenReturn(JAVA_WS_URL); - when(request.getMethod()).thenReturn("GET"); - - underTest.verifyState(request, null, LOGIN); - } - - @Test - public void ignore_not_api_requests() { - executeVerifyStateDoesNotFailOnRequest("/events", "POST"); - executeVerifyStateDoesNotFailOnRequest("/favorites", "POST"); - } - - @Test - public void refresh_state() { - underTest.refreshState(request, response, CSRF_STATE, 30); - - verify(response).addCookie(cookieArgumentCaptor.capture()); - verifyCookie(cookieArgumentCaptor.getValue()); - } - - @Test - public void remove_state() { - underTest.removeState(request, response); - - verify(response).addCookie(cookieArgumentCaptor.capture()); - Cookie cookie = cookieArgumentCaptor.getValue(); - assertThat(cookie.getValue()).isNull(); - assertThat(cookie.getMaxAge()).isEqualTo(0); - } - - private void verifyCookie(Cookie cookie) { - assertThat(cookie.getName()).isEqualTo("XSRF-TOKEN"); - assertThat(cookie.getValue()).isNotEmpty(); - assertThat(cookie.getPath()).isEqualTo("/"); - assertThat(cookie.isHttpOnly()).isFalse(); - assertThat(cookie.getMaxAge()).isEqualTo(TIMEOUT); - assertThat(cookie.getSecure()).isFalse(); - } - - private void mockPostJavaWsRequest() { - when(request.getRequestURI()).thenReturn(JAVA_WS_URL); - when(request.getMethod()).thenReturn("POST"); - } - - private void mockRequestCsrf(String csrfState) { - when(request.getHeader("X-XSRF-TOKEN")).thenReturn(csrfState); - } - - private void executeVerifyStateDoesNotFailOnRequest(String uri, String method) { - when(request.getRequestURI()).thenReturn(uri); - when(request.getMethod()).thenReturn(method); - - underTest.verifyState(request, null, LOGIN); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtHttpHandlerTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtHttpHandlerTest.java deleted file mode 100644 index ed1f5c22084..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtHttpHandlerTest.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.impl.DefaultClaims; -import java.util.Date; -import java.util.Optional; -import javax.annotation.Nullable; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.user.UserDto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -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 JwtHttpHandlerTest { - - private static final String JWT_TOKEN = "TOKEN"; - private static final String CSRF_STATE = "CSRF_STATE"; - - private static final long NOW = 10_000_000_000L; - private static final long FOUR_MINUTES_AGO = NOW - 4 * 60 * 1000L; - private static final long SIX_MINUTES_AGO = NOW - 6 * 60 * 1000L; - private static final long TEN_DAYS_AGO = NOW - 10 * 24 * 60 * 60 * 1000L; - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Rule - public DbTester db = DbTester.create(); - - private DbClient dbClient = db.getDbClient(); - private DbSession dbSession = db.getSession(); - private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class); - private ArgumentCaptor<JwtSerializer.JwtSession> jwtArgumentCaptor = ArgumentCaptor.forClass(JwtSerializer.JwtSession.class); - private HttpServletRequest request = mock(HttpServletRequest.class); - private HttpServletResponse response = mock(HttpServletResponse.class); - private HttpSession httpSession = mock(HttpSession.class); - private System2 system2 = spy(System2.INSTANCE); - private MapSettings settings = new MapSettings(); - private JwtSerializer jwtSerializer = mock(JwtSerializer.class); - private JwtCsrfVerifier jwtCsrfVerifier = mock(JwtCsrfVerifier.class); - - private JwtHttpHandler underTest = new JwtHttpHandler(system2, dbClient, settings.asConfig(), jwtSerializer, jwtCsrfVerifier); - - @Before - public void setUp() throws Exception { - when(system2.now()).thenReturn(NOW); - when(request.getSession()).thenReturn(httpSession); - when(jwtSerializer.encode(any(JwtSerializer.JwtSession.class))).thenReturn(JWT_TOKEN); - when(jwtCsrfVerifier.generateState(eq(request), eq(response), anyInt())).thenReturn(CSRF_STATE); - } - - @Test - public void create_token() { - UserDto user = db.users().insertUser(); - underTest.generateToken(user, request, response); - - Optional<Cookie> jwtCookie = findCookie("JWT-SESSION"); - assertThat(jwtCookie).isPresent(); - verifyCookie(jwtCookie.get(), JWT_TOKEN, 3 * 24 * 60 * 60); - - verify(jwtSerializer).encode(jwtArgumentCaptor.capture()); - verifyToken(jwtArgumentCaptor.getValue(), user, 3 * 24 * 60 * 60, NOW); - } - - @Test - public void generate_csrf_state_when_creating_token() { - UserDto user = db.users().insertUser(); - underTest.generateToken(user, request, response); - - verify(jwtCsrfVerifier).generateState(request, response, 3 * 24 * 60 * 60); - - verify(jwtSerializer).encode(jwtArgumentCaptor.capture()); - JwtSerializer.JwtSession token = jwtArgumentCaptor.getValue(); - assertThat(token.getProperties().get("xsrfToken")).isEqualTo(CSRF_STATE); - } - - @Test - public void generate_token_is_using_session_timeout_from_settings() { - UserDto user = db.users().insertUser(); - int sessionTimeoutInMinutes = 10; - settings.setProperty("sonar.web.sessionTimeoutInMinutes", sessionTimeoutInMinutes); - - underTest = new JwtHttpHandler(system2, dbClient, settings.asConfig(), jwtSerializer, jwtCsrfVerifier); - underTest.generateToken(user, request, response); - - verify(jwtSerializer).encode(jwtArgumentCaptor.capture()); - verifyToken(jwtArgumentCaptor.getValue(), user, sessionTimeoutInMinutes * 60, NOW); - } - - @Test - public void session_timeout_property_cannot_be_updated() { - UserDto user = db.users().insertUser(); - int firstSessionTimeoutInMinutes = 10; - settings.setProperty("sonar.web.sessionTimeoutInMinutes", firstSessionTimeoutInMinutes); - - underTest = new JwtHttpHandler(system2, dbClient, settings.asConfig(), jwtSerializer, jwtCsrfVerifier); - underTest.generateToken(user, request, response); - - // The property is updated, but it won't be taking into account - settings.setProperty("sonar.web.sessionTimeoutInMinutes", 15); - underTest.generateToken(user, request, response); - verify(jwtSerializer, times(2)).encode(jwtArgumentCaptor.capture()); - verifyToken(jwtArgumentCaptor.getAllValues().get(0), user,firstSessionTimeoutInMinutes * 60, NOW); - verifyToken(jwtArgumentCaptor.getAllValues().get(1), user, firstSessionTimeoutInMinutes * 60, NOW); - } - - @Test - public void session_timeout_property_cannot_be_zero() { - settings.setProperty("sonar.web.sessionTimeoutInMinutes", 0); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Property sonar.web.sessionTimeoutInMinutes must be strictly positive. Got 0"); - - new JwtHttpHandler(system2, dbClient, settings.asConfig(), jwtSerializer, jwtCsrfVerifier); - } - - @Test - public void session_timeout_property_cannot_be_negative() { - settings.setProperty("sonar.web.sessionTimeoutInMinutes", -10); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Property sonar.web.sessionTimeoutInMinutes must be strictly positive. Got -10"); - - new JwtHttpHandler(system2, dbClient, settings.asConfig(), jwtSerializer, jwtCsrfVerifier); - } - - @Test - public void session_timeout_property_cannot_be_greater_than_three_months() { - settings.setProperty("sonar.web.sessionTimeoutInMinutes", 4 * 30 * 24 * 60); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Property sonar.web.sessionTimeoutInMinutes must not be greater than 3 months (129600 minutes). Got 172800 minutes"); - - new JwtHttpHandler(system2, dbClient, settings.asConfig(), jwtSerializer, jwtCsrfVerifier); - } - - @Test - public void validate_token() { - UserDto user = db.users().insertUser(); - addJwtCookie(); - Claims claims = createToken(user.getUuid(), NOW); - when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims)); - - assertThat(underTest.validateToken(request, response).isPresent()).isTrue(); - - verify(jwtSerializer, never()).encode(any(JwtSerializer.JwtSession.class)); - } - - @Test - public void validate_token_refresh_session_when_refresh_time_is_reached() { - UserDto user = db.users().insertUser(); - addJwtCookie(); - // Token was created 10 days ago and refreshed 6 minutes ago - Claims claims = createToken(user.getUuid(), TEN_DAYS_AGO); - claims.put("lastRefreshTime", SIX_MINUTES_AGO); - when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims)); - - assertThat(underTest.validateToken(request, response).isPresent()).isTrue(); - - verify(jwtSerializer).refresh(any(Claims.class), eq(3 * 24 * 60 * 60)); - } - - @Test - public void validate_token_does_not_refresh_session_when_refresh_time_is_not_reached() { - UserDto user = db.users().insertUser(); - addJwtCookie(); - // Token was created 10 days ago and refreshed 4 minutes ago - Claims claims = createToken(user.getUuid(), TEN_DAYS_AGO); - claims.put("lastRefreshTime", FOUR_MINUTES_AGO); - when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims)); - - assertThat(underTest.validateToken(request, response).isPresent()).isTrue(); - - verify(jwtSerializer, never()).refresh(any(Claims.class), anyInt()); - } - - @Test - public void validate_token_does_not_refresh_session_when_disconnected_timeout_is_reached() { - UserDto user = db.users().insertUser(); - addJwtCookie(); - // Token was created 4 months ago, refreshed 4 minutes ago, and it expired in 5 minutes - Claims claims = createToken(user.getUuid(), NOW - (4L * 30 * 24 * 60 * 60 * 1000)); - claims.setExpiration(new Date(NOW + 5 * 60 * 1000)); - claims.put("lastRefreshTime", FOUR_MINUTES_AGO); - when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims)); - - assertThat(underTest.validateToken(request, response).isPresent()).isFalse(); - } - - @Test - public void validate_token_does_not_refresh_session_when_user_is_disabled() { - addJwtCookie(); - UserDto user = addUser(false); - - Claims claims = createToken(user.getLogin(), NOW); - when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims)); - - assertThat(underTest.validateToken(request, response).isPresent()).isFalse(); - } - - @Test - public void validate_token_does_not_refresh_session_when_token_is_no_more_valid() { - addJwtCookie(); - - when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.empty()); - - assertThat(underTest.validateToken(request, response).isPresent()).isFalse(); - } - - @Test - public void validate_token_does_nothing_when_no_jwt_cookie() { - underTest.validateToken(request, response); - - verifyZeroInteractions(httpSession, jwtSerializer); - assertThat(underTest.validateToken(request, response).isPresent()).isFalse(); - } - - @Test - public void validate_token_does_nothing_when_empty_value_in_jwt_cookie() { - when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("JWT-SESSION", "")}); - - underTest.validateToken(request, response); - - verifyZeroInteractions(httpSession, jwtSerializer); - assertThat(underTest.validateToken(request, response).isPresent()).isFalse(); - } - - @Test - public void validate_token_verify_csrf_state() { - UserDto user = db.users().insertUser(); - addJwtCookie(); - Claims claims = createToken(user.getUuid(), NOW); - claims.put("xsrfToken", CSRF_STATE); - when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims)); - - underTest.validateToken(request, response); - - verify(jwtCsrfVerifier).verifyState(request, CSRF_STATE, user.getUuid()); - } - - @Test - public void validate_token_refresh_state_when_refreshing_token() { - UserDto user = db.users().insertUser(); - addJwtCookie(); - - // Token was created 10 days ago and refreshed 6 minutes ago - Claims claims = createToken(user.getUuid(), TEN_DAYS_AGO); - claims.put("xsrfToken", "CSRF_STATE"); - when(jwtSerializer.decode(JWT_TOKEN)).thenReturn(Optional.of(claims)); - - underTest.validateToken(request, response); - - verify(jwtSerializer).refresh(any(Claims.class), anyInt()); - verify(jwtCsrfVerifier).refreshState(request, response, "CSRF_STATE", 3 * 24 * 60 * 60); - } - - @Test - public void remove_token() { - underTest.removeToken(request, response); - - verifyCookie(findCookie("JWT-SESSION").get(), null, 0); - verify(jwtCsrfVerifier).removeState(request, response); - } - - private void verifyToken(JwtSerializer.JwtSession token, UserDto user, int expectedExpirationTime, long expectedRefreshTime) { - assertThat(token.getExpirationTimeInSeconds()).isEqualTo(expectedExpirationTime); - assertThat(token.getUserLogin()).isEqualTo(user.getUuid()); - assertThat(token.getProperties().get("lastRefreshTime")).isEqualTo(expectedRefreshTime); - } - - private Optional<Cookie> findCookie(String name) { - verify(response).addCookie(cookieArgumentCaptor.capture()); - return cookieArgumentCaptor.getAllValues().stream() - .filter(cookie -> name.equals(cookie.getName())) - .findFirst(); - } - - private void verifyCookie(Cookie cookie, @Nullable String value, int expiry) { - assertThat(cookie.getPath()).isEqualTo("/"); - assertThat(cookie.isHttpOnly()).isTrue(); - assertThat(cookie.getMaxAge()).isEqualTo(expiry); - assertThat(cookie.getSecure()).isFalse(); - assertThat(cookie.getValue()).isEqualTo(value); - } - - private UserDto addUser(boolean active) { - UserDto user = newUserDto() - .setActive(active); - dbClient.userDao().insert(dbSession, user); - dbSession.commit(); - return user; - } - - private Cookie addJwtCookie() { - Cookie cookie = new Cookie("JWT-SESSION", JWT_TOKEN); - when(request.getCookies()).thenReturn(new Cookie[] {cookie}); - return cookie; - } - - private Claims createToken(String userUuid, long createdAt) { - // Expired in 5 minutes by default - return createToken(userUuid, createdAt, NOW + 5 * 60 * 1000); - } - - private Claims createToken(String userUuid, long createdAt, long expiredAt) { - DefaultClaims claims = new DefaultClaims(); - claims.setId("ID"); - claims.setSubject(userUuid); - claims.setIssuedAt(new Date(createdAt)); - claims.setExpiration(new Date(expiredAt)); - claims.put("lastRefreshTime", createdAt); - return claims; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtSerializerTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtSerializerTest.java deleted file mode 100644 index 08aae1e567e..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/JwtSerializerTest.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.collect.ImmutableMap; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.impl.DefaultClaims; -import java.util.Base64; -import java.util.Date; -import java.util.Optional; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.utils.DateUtils; -import org.sonar.api.utils.System2; -import org.sonar.core.util.UuidFactory; -import org.sonar.core.util.UuidFactoryImpl; -import org.sonar.server.authentication.JwtSerializer.JwtSession; -import org.sonar.server.authentication.event.AuthenticationEvent.Source; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException; - -public class JwtSerializerTest { - - private static final String A_SECRET_KEY = "HrPSavOYLNNrwTY+SOqpChr7OwvbR/zbDLdVXRN0+Eg="; - private static final String USER_LOGIN = "john"; - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private MapSettings settings = new MapSettings(); - private System2 system2 = System2.INSTANCE; - private UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE; - private JwtSerializer underTest = new JwtSerializer(settings.asConfig(), system2, uuidFactory); - - @Test - public void generate_token() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - - String token = underTest.encode(new JwtSession(USER_LOGIN, 10)); - - assertThat(token).isNotEmpty(); - } - - @Test - public void generate_token_with_expiration_date() { - setSecretKey(A_SECRET_KEY); - - underTest.start(); - Date now = new Date(); - - long expirationTimeInSeconds = 10L; - String token = underTest.encode(new JwtSession(USER_LOGIN, expirationTimeInSeconds)); - - assertThat(token).isNotEmpty(); - Claims claims = underTest.decode(token).get(); - assertThat(claims.getExpiration().getTime()).isGreaterThanOrEqualTo(now.getTime() + expirationTimeInSeconds * 1000L - 1000L); - } - - @Test - public void generate_token_with_big_expiration_date() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - Date now = new Date(); - - long oneYearInSeconds = 12 * 30 * 24 * 60 * 60L; - String token = underTest.encode(new JwtSession(USER_LOGIN, oneYearInSeconds)); - - assertThat(token).isNotEmpty(); - Claims claims = underTest.decode(token).get(); - // Check expiration date it set to one year in the future - assertThat(claims.getExpiration().getTime()).isGreaterThanOrEqualTo(now.getTime() + oneYearInSeconds * 1000L - 1000L); - } - - @Test - public void generate_token_with_property() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - - String token = underTest.encode(new JwtSession(USER_LOGIN, 10, ImmutableMap.of("custom", "property"))); - - assertThat(token).isNotEmpty(); - Claims claims = underTest.decode(token).get(); - assertThat(claims.get("custom")).isEqualTo("property"); - } - - @Test - public void decode_token() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - Date now = new Date(); - - String token = underTest.encode(new JwtSession(USER_LOGIN, 20 * 60)); - - Claims claims = underTest.decode(token).get(); - assertThat(claims.getId()).isNotEmpty(); - assertThat(claims.getSubject()).isEqualTo(USER_LOGIN); - assertThat(claims.getExpiration()).isNotNull(); - assertThat(claims.getIssuedAt()).isNotNull(); - // Check expiration date it set to more than 19 minutes in the future - assertThat(claims.getExpiration()).isAfterOrEqualsTo(new Date(now.getTime() + 19 * 60 * 1000)); - } - - @Test - public void return_no_token_when_expiration_date_is_reached() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - - String token = Jwts.builder() - .setId("123") - .setIssuedAt(new Date(system2.now())) - .setExpiration(new Date(system2.now())) - .signWith(SignatureAlgorithm.HS256, decodeSecretKey(A_SECRET_KEY)) - .compact(); - - assertThat(underTest.decode(token)).isEmpty(); - } - - @Test - public void return_no_token_when_secret_key_has_changed() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - - String token = Jwts.builder() - .setId("123") - .setSubject(USER_LOGIN) - .setIssuedAt(new Date(system2.now())) - .setExpiration(new Date(system2.now() + 20 * 60 * 1000)) - .signWith(SignatureAlgorithm.HS256, decodeSecretKey("LyWgHktP0FuHB2K+kMs3KWMCJyFHVZDdDSqpIxAMVaQ=")) - .compact(); - - assertThat(underTest.decode(token)).isEmpty(); - } - - @Test - public void fail_to_decode_token_when_no_id() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - - String token = Jwts.builder() - .setSubject(USER_LOGIN) - .setIssuer("sonarqube") - .setIssuedAt(new Date(system2.now())) - .setExpiration(new Date(system2.now() + 20 * 60 * 1000)) - .signWith(SignatureAlgorithm.HS256, decodeSecretKey(A_SECRET_KEY)) - .compact(); - - expectedException.expect(authenticationException().from(Source.jwt()).withLogin(USER_LOGIN).andNoPublicMessage()); - expectedException.expectMessage("Token id hasn't been found"); - underTest.decode(token); - } - - @Test - public void fail_to_decode_token_when_no_subject() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - - String token = Jwts.builder() - .setId("123") - .setIssuer("sonarqube") - .setIssuedAt(new Date(system2.now())) - .setExpiration(new Date(system2.now() + 20 * 60 * 1000)) - .signWith(SignatureAlgorithm.HS256, decodeSecretKey(A_SECRET_KEY)) - .compact(); - - expectedException.expect(authenticationException().from(Source.jwt()).withoutLogin().andNoPublicMessage()); - expectedException.expectMessage("Token subject hasn't been found"); - underTest.decode(token); - } - - @Test - public void fail_to_decode_token_when_no_expiration_date() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - - String token = Jwts.builder() - .setId("123") - .setIssuer("sonarqube") - .setSubject(USER_LOGIN) - .setIssuedAt(new Date(system2.now())) - .signWith(SignatureAlgorithm.HS256, decodeSecretKey(A_SECRET_KEY)) - .compact(); - - expectedException.expect(authenticationException().from(Source.jwt()).withLogin(USER_LOGIN).andNoPublicMessage()); - expectedException.expectMessage("Token expiration date hasn't been found"); - underTest.decode(token); - } - - @Test - public void fail_to_decode_token_when_no_creation_date() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - - String token = Jwts.builder() - .setId("123") - .setSubject(USER_LOGIN) - .setExpiration(new Date(system2.now() + 20 * 60 * 1000)) - .signWith(SignatureAlgorithm.HS256, decodeSecretKey(A_SECRET_KEY)) - .compact(); - - expectedException.expect(authenticationException().from(Source.jwt()).withLogin(USER_LOGIN).andNoPublicMessage()); - expectedException.expectMessage("Token creation date hasn't been found"); - underTest.decode(token); - } - - @Test - public void generate_new_secret_key_if_not_set_by_settings() { - assertThat(underTest.getSecretKey()).isNull(); - - underTest.start(); - - assertThat(underTest.getSecretKey()).isNotNull(); - assertThat(underTest.getSecretKey().getAlgorithm()).isEqualTo(SignatureAlgorithm.HS256.getJcaName()); - } - - @Test - public void load_secret_key_from_settings() { - setSecretKey(A_SECRET_KEY); - - underTest.start(); - - assertThat(settings.getString("sonar.auth.jwtBase64Hs256Secret")).isEqualTo(A_SECRET_KEY); - } - - @Test - public void refresh_token() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - - Date now = new Date(); - Date createdAt = DateUtils.parseDate("2016-01-01"); - // Expired in 10 minutes - Date expiredAt = new Date(now.getTime() + 10 * 60 * 1000); - Claims token = new DefaultClaims() - .setId("id") - .setSubject("subject") - .setIssuer("sonarqube") - .setIssuedAt(createdAt) - .setExpiration(expiredAt); - token.put("key", "value"); - - // Refresh the token with a higher expiration time - String encodedToken = underTest.refresh(token, 20 * 60); - - Claims result = underTest.decode(encodedToken).get(); - assertThat(result.getId()).isEqualTo("id"); - assertThat(result.getSubject()).isEqualTo("subject"); - assertThat(result.getIssuer()).isEqualTo("sonarqube"); - assertThat(result.getIssuedAt()).isEqualTo(createdAt); - assertThat(result.get("key")).isEqualTo("value"); - // Expiration date has been changed - assertThat(result.getExpiration()).isNotEqualTo(expiredAt) - .isAfterOrEqualsTo(new Date(now.getTime() + 19 * 1000)); - } - - @Test - public void refresh_token_generate_a_new_hash() { - setSecretKey(A_SECRET_KEY); - underTest.start(); - String token = underTest.encode(new JwtSession(USER_LOGIN, 30)); - Optional<Claims> claims = underTest.decode(token); - - String newToken = underTest.refresh(claims.get(), 45); - - assertThat(newToken).isNotEqualTo(token); - } - - @Test - public void encode_fail_when_not_started() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("org.sonar.server.authentication.JwtSerializer not started"); - - underTest.encode(new JwtSession(USER_LOGIN, 10)); - } - - @Test - public void decode_fail_when_not_started() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("org.sonar.server.authentication.JwtSerializer not started"); - - underTest.decode("token"); - } - - @Test - public void refresh_fail_when_not_started() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("org.sonar.server.authentication.JwtSerializer not started"); - - underTest.refresh(new DefaultClaims(), 10); - } - - private SecretKey decodeSecretKey(String encodedKey) { - byte[] decodedKey = Base64.getDecoder().decode(encodedKey); - return new SecretKeySpec(decodedKey, 0, decodedKey.length, SignatureAlgorithm.HS256.getJcaName()); - } - - private void setSecretKey(String s) { - settings.setProperty("sonar.auth.jwtBase64Hs256Secret", s); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/LogOAuthWarningTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/LogOAuthWarningTest.java deleted file mode 100644 index 50d26ce5d8d..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/LogOAuthWarningTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.platform.Server; -import org.sonar.api.server.authentication.OAuth2IdentityProvider; -import org.sonar.api.utils.log.LogTester; -import org.sonar.api.utils.log.LoggerLevel; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - - -public class LogOAuthWarningTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Rule - public LogTester logTester = new LogTester(); - - - private Server server = mock(Server.class); - - @Test - public void log_warning_at_startup_if_non_secured_base_url_and_oauth_is_installed() { - when(server.getPublicRootUrl()).thenReturn("http://mydomain.com"); - - LogOAuthWarning underTest = new LogOAuthWarning(server, new OAuth2IdentityProvider[1]); - - underTest.start(); - - assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly("For security reasons, OAuth authentication should use HTTPS. You should set the property 'Administration > Configuration > Server base URL' to a HTTPS URL."); - - underTest.stop(); - } - - @Test - public void do_not_log_warning_at_startup_if_secured_base_url_and_oauth_is_installed() { - when(server.getPublicRootUrl()).thenReturn("https://mydomain.com"); - - LogOAuthWarning underTest = new LogOAuthWarning(server, new OAuth2IdentityProvider[1]); - - underTest.start(); - - assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty(); - - underTest.stop(); - } - - @Test - public void do_not_log_warning_at_startup_if_non_secured_base_url_but_oauth_is_not_installed() { - when(server.getPublicRootUrl()).thenReturn("http://mydomain.com"); - - LogOAuthWarning underTest = new LogOAuthWarning(server); - - underTest.start(); - - assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty(); - - underTest.stop(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImplTest.java deleted file mode 100644 index 07a6fbe79e8..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2AuthenticationParametersImplTest.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Optional; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class OAuth2AuthenticationParametersImplTest { - - private static final String AUTHENTICATION_COOKIE_NAME = "AUTH-PARAMS"; - private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class); - - private HttpServletResponse response = mock(HttpServletResponse.class); - private HttpServletRequest request = mock(HttpServletRequest.class); - - private OAuth2AuthenticationParameters underTest = new OAuth2AuthenticationParametersImpl(); - - @Before - public void setUp() throws Exception { - when(request.getContextPath()).thenReturn(""); - } - - @Test - public void init_create_cookie() { - when(request.getParameter("return_to")).thenReturn("/settings"); - - underTest.init(request, response); - - verify(response).addCookie(cookieArgumentCaptor.capture()); - Cookie cookie = cookieArgumentCaptor.getValue(); - assertThat(cookie.getName()).isEqualTo(AUTHENTICATION_COOKIE_NAME); - assertThat(cookie.getValue()).isNotEmpty(); - assertThat(cookie.getPath()).isEqualTo("/"); - assertThat(cookie.isHttpOnly()).isTrue(); - assertThat(cookie.getMaxAge()).isEqualTo(300); - assertThat(cookie.getSecure()).isFalse(); - } - - @Test - public void init_does_not_create_cookie_when_no_parameter() { - underTest.init(request, response); - - verify(response, never()).addCookie(any(Cookie.class)); - } - - @Test - public void init_does_not_create_cookie_when_parameters_are_empty() { - when(request.getParameter("return_to")).thenReturn(""); - when(request.getParameter("allowEmailShift")).thenReturn(""); - - underTest.init(request, response); - - verify(response, never()).addCookie(any(Cookie.class)); - } - - @Test - public void init_does_not_create_cookie_when_parameters_are_null() { - when(request.getParameter("return_to")).thenReturn(null); - when(request.getParameter("allowEmailShift")).thenReturn(null); - - underTest.init(request, response); - - verify(response, never()).addCookie(any(Cookie.class)); - } - - @Test - public void return_to_is_not_set_when_not_local() { - when(request.getParameter("return_to")).thenReturn("http://external_url"); - underTest.init(request, response); - verify(response, never()).addCookie(any()); - - when(request.getParameter("return_to")).thenReturn("//local_file"); - underTest.init(request, response); - verify(response, never()).addCookie(any()); - - when(request.getParameter("return_to")).thenReturn("/\\local_file"); - underTest.init(request, response); - verify(response, never()).addCookie(any()); - - when(request.getParameter("return_to")).thenReturn("something_else"); - underTest.init(request, response); - verify(response, never()).addCookie(any()); - } - - @Test - public void get_return_to_parameter() { - when(request.getCookies()).thenReturn(new Cookie[] {new Cookie(AUTHENTICATION_COOKIE_NAME, "{\"return_to\":\"/settings\"}")}); - - Optional<String> redirection = underTest.getReturnTo(request); - - assertThat(redirection).isNotEmpty(); - assertThat(redirection.get()).isEqualTo("/settings"); - } - - @Test - public void get_return_to_is_empty_when_no_cookie() { - when(request.getCookies()).thenReturn(new Cookie[] {}); - - Optional<String> redirection = underTest.getReturnTo(request); - - assertThat(redirection).isEmpty(); - } - - @Test - public void get_return_to_is_empty_when_no_value() { - 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\"}")}); - - underTest.delete(request, response); - - verify(response).addCookie(cookieArgumentCaptor.capture()); - Cookie updatedCookie = cookieArgumentCaptor.getValue(); - assertThat(updatedCookie.getName()).isEqualTo(AUTHENTICATION_COOKIE_NAME); - assertThat(updatedCookie.getValue()).isNull(); - assertThat(updatedCookie.getPath()).isEqualTo("/"); - assertThat(updatedCookie.getMaxAge()).isEqualTo(0); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java deleted file mode 100644 index a42f29cf059..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.FilterChain; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -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 org.sonar.server.user.ThreadLocalUserSession; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -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 { - - private static final String OAUTH2_PROVIDER_KEY = "github"; - private static final String LOGIN = "foo"; - - @Rule - public LogTester logTester = new LogTester(); - @Rule - public ExpectedException thrown = ExpectedException.none(); - @Rule - public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule(); - - private OAuth2ContextFactory oAuth2ContextFactory = mock(OAuth2ContextFactory.class); - - private HttpServletRequest request = mock(HttpServletRequest.class); - private HttpServletResponse response = mock(HttpServletResponse.class); - private FilterChain chain = mock(FilterChain.class); - - private FakeOAuth2IdentityProvider oAuth2IdentityProvider = new WellbehaveFakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true, LOGIN); - private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - private OAuth2AuthenticationParameters oAuthRedirection = mock(OAuth2AuthenticationParameters.class); - private ThreadLocalUserSession threadLocalUserSession = mock(ThreadLocalUserSession.class); - - private ArgumentCaptor<AuthenticationException> authenticationExceptionCaptor = ArgumentCaptor.forClass(AuthenticationException.class); - private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class); - - private OAuth2CallbackFilter underTest = new OAuth2CallbackFilter(identityProviderRepository, oAuth2ContextFactory, authenticationEvent, oAuthRedirection, - threadLocalUserSession); - - @Before - public void setUp() throws Exception { - when(oAuth2ContextFactory.newCallback(request, response, oAuth2IdentityProvider)).thenReturn(mock(OAuth2IdentityProvider.CallbackContext.class)); - when(request.getContextPath()).thenReturn(""); - } - - @Test - public void do_get_pattern() { - assertThat(underTest.doGetPattern()).isNotNull(); - } - - @Test - public void do_filter_with_context() { - when(request.getContextPath()).thenReturn("/sonarqube"); - when(request.getRequestURI()).thenReturn("/sonarqube/oauth2/callback/" + OAUTH2_PROVIDER_KEY); - identityProviderRepository.addIdentityProvider(oAuth2IdentityProvider); - when(threadLocalUserSession.hasSession()).thenReturn(true); - when(threadLocalUserSession.getLogin()).thenReturn(LOGIN); - - underTest.doFilter(request, response, chain); - - assertCallbackCalled(oAuth2IdentityProvider); - verify(authenticationEvent).loginSuccess(request, LOGIN, Source.oauth2(oAuth2IdentityProvider)); - } - - @Test - public void do_filter_with_context_no_log_if_provider_did_not_call_authenticate_on_context() { - when(request.getContextPath()).thenReturn("/sonarqube"); - when(request.getRequestURI()).thenReturn("/sonarqube/oauth2/callback/" + OAUTH2_PROVIDER_KEY); - FakeOAuth2IdentityProvider identityProvider = new FakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, true); - identityProviderRepository.addIdentityProvider(identityProvider); - - underTest.doFilter(request, response, chain); - - assertCallbackCalled(identityProvider); - verify(authenticationEvent).loginFailure(eq(request), authenticationExceptionCaptor.capture()); - AuthenticationException authenticationException = authenticationExceptionCaptor.getValue(); - assertThat(authenticationException).hasMessage("Plugin did not call authenticate"); - assertThat(authenticationException.getSource()).isEqualTo(Source.oauth2(identityProvider)); - assertThat(authenticationException.getLogin()).isNull(); - assertThat(authenticationException.getPublicMessage()).isNull(); - } - - @Test - public void do_filter_on_auth2_identity_provider() { - when(request.getRequestURI()).thenReturn("/oauth2/callback/" + OAUTH2_PROVIDER_KEY); - identityProviderRepository.addIdentityProvider(oAuth2IdentityProvider); - when(threadLocalUserSession.hasSession()).thenReturn(true); - when(threadLocalUserSession.getLogin()).thenReturn(LOGIN); - - underTest.doFilter(request, response, chain); - - assertCallbackCalled(oAuth2IdentityProvider); - verify(authenticationEvent).loginSuccess(request, LOGIN, Source.oauth2(oAuth2IdentityProvider)); - } - - @Test - public void fail_on_not_oauth2_provider() throws Exception { - String providerKey = "openid"; - when(request.getRequestURI()).thenReturn("/oauth2/callback/" + providerKey); - identityProviderRepository.addIdentityProvider(new FakeBasicIdentityProvider(providerKey, true)); - - underTest.doFilter(request, response, chain); - - assertError("Not an OAuth2IdentityProvider: class org.sonar.server.authentication.FakeBasicIdentityProvider"); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void fail_on_disabled_provider() throws Exception { - when(request.getRequestURI()).thenReturn("/oauth2/callback/" + OAUTH2_PROVIDER_KEY); - identityProviderRepository.addIdentityProvider(new FakeOAuth2IdentityProvider(OAUTH2_PROVIDER_KEY, false)); - - underTest.doFilter(request, response, chain); - - assertError("Failed to retrieve IdentityProvider for key 'github'"); - verifyZeroInteractions(authenticationEvent); - } - - @Test - public void redirect_when_failing_because_of_UnauthorizedExceptionException() throws Exception { - FailWithUnauthorizedExceptionIdProvider identityProvider = new FailWithUnauthorizedExceptionIdProvider(); - when(request.getRequestURI()).thenReturn("/oauth2/callback/" + identityProvider.getKey()); - identityProviderRepository.addIdentityProvider(identityProvider); - - underTest.doFilter(request, response, chain); - - verify(response).sendRedirect("/sessions/unauthorized"); - verify(authenticationEvent).loginFailure(eq(request), authenticationExceptionCaptor.capture()); - AuthenticationException authenticationException = authenticationExceptionCaptor.getValue(); - assertThat(authenticationException).hasMessage("Email john@email.com is already used"); - assertThat(authenticationException.getSource()).isEqualTo(Source.oauth2(identityProvider)); - assertThat(authenticationException.getLogin()).isNull(); - assertThat(authenticationException.getPublicMessage()).isEqualTo("Email john@email.com is already used"); - 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()).isEqualTo("Email%20john%40email.com%20is%20already%20used"); - assertThat(cookie.getPath()).isEqualTo("/"); - assertThat(cookie.isHttpOnly()).isFalse(); - assertThat(cookie.getMaxAge()).isEqualTo(300); - assertThat(cookie.getSecure()).isFalse(); - } - - @Test - public void redirect_with_context_path_when_failing_because_of_UnauthorizedExceptionException() throws Exception { - when(request.getContextPath()).thenReturn("/sonarqube"); - FailWithUnauthorizedExceptionIdProvider identityProvider = new FailWithUnauthorizedExceptionIdProvider(); - when(request.getRequestURI()).thenReturn("/sonarqube/oauth2/callback/" + identityProvider.getKey()); - identityProviderRepository.addIdentityProvider(identityProvider); - - underTest.doFilter(request, response, chain); - - verify(response).sendRedirect("/sonarqube/sessions/unauthorized"); - verify(oAuthRedirection).delete(eq(request), eq(response)); - } - - @Test - public void redirect_when_failing_because_of_Exception() throws Exception { - FailWithIllegalStateException identityProvider = new FailWithIllegalStateException(); - when(request.getRequestURI()).thenReturn("/oauth2/callback/" + identityProvider.getKey()); - identityProviderRepository.addIdentityProvider(identityProvider); - - underTest.doFilter(request, response, chain); - - verify(response).sendRedirect("/sessions/unauthorized"); - assertThat(logTester.logs(LoggerLevel.WARN)).containsExactlyInAnyOrder("Fail to callback authentication with 'failing'"); - verify(oAuthRedirection).delete(eq(request), eq(response)); - } - - @Test - public void redirect_with_context_when_failing_because_of_Exception() throws Exception { - when(request.getContextPath()).thenReturn("/sonarqube"); - FailWithIllegalStateException identityProvider = new FailWithIllegalStateException(); - when(request.getRequestURI()).thenReturn("/oauth2/callback/" + identityProvider.getKey()); - identityProviderRepository.addIdentityProvider(identityProvider); - - underTest.doFilter(request, response, chain); - - 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"); - - underTest.doFilter(request, response, chain); - - assertError("No provider key found in URI"); - verifyZeroInteractions(authenticationEvent); - } - - private void assertCallbackCalled(FakeOAuth2IdentityProvider oAuth2IdentityProvider) { - assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty(); - assertThat(oAuth2IdentityProvider.isCallbackCalled()).isTrue(); - } - - private void assertError(String expectedError) throws Exception { - assertThat(logTester.logs(LoggerLevel.WARN)).contains(expectedError); - verify(response).sendRedirect("/sessions/unauthorized"); - assertThat(oAuth2IdentityProvider.isInitCalled()).isFalse(); - } - - private static class FailWithUnauthorizedExceptionIdProvider extends FailingIdentityProvider { - @Override - public void callback(CallbackContext context) { - throw new UnauthorizedException("Email john@email.com is already used"); - } - } - - private static class FailWithIllegalStateException extends FailingIdentityProvider { - @Override - public void callback(CallbackContext context) { - throw new IllegalStateException("Failure !"); - } - } - - 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") - .setLogin("john.github") - .setName(existingUser.getName()) - .setEmail(existingUser.getEmail()) - .build(), this); - } - } - - private static abstract class FailingIdentityProvider extends TestIdentityProvider implements OAuth2IdentityProvider { - FailingIdentityProvider() { - this.setKey("failing"); - this.setName("Failing"); - this.setEnabled(true); - } - - @Override - public void init(InitContext context) { - // Nothing to do - } - } - - /** - * An extension of {@link FakeOAuth2IdentityProvider} that actually call {@link org.sonar.api.server.authentication.OAuth2IdentityProvider.CallbackContext#authenticate(UserIdentity)}. - */ - private static class WellbehaveFakeOAuth2IdentityProvider extends FakeOAuth2IdentityProvider { - private final String login; - - public WellbehaveFakeOAuth2IdentityProvider(String key, boolean enabled, String login) { - super(key, enabled); - this.login = login; - } - - @Override - public void callback(CallbackContext context) { - super.callback(context); - context.authenticate(UserIdentity.builder() - .setLogin(login) - .setProviderLogin(login) - .setEmail(login + "@toto.com") - .setName("name of " + login) - .build()); - } - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2ContextFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2ContextFactoryTest.java deleted file mode 100644 index cf2eb5f19fe..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2ContextFactoryTest.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.collect.ImmutableSet; -import java.util.Optional; -import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -import org.sonar.api.platform.Server; -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.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; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class OAuth2ContextFactoryTest { - - private static final String PROVIDER_KEY = "github"; - private static final String SECURED_PUBLIC_ROOT_URL = "https://mydomain.com"; - private static final String PROVIDER_NAME = "provider name"; - private static final UserIdentity USER_IDENTITY = UserIdentity.builder() - .setProviderId("ABCD") - .setProviderLogin("johndoo") - .setLogin("id:johndoo") - .setName("John") - .setEmail("john@email.com") - .build(); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private ThreadLocalUserSession threadLocalUserSession = mock(ThreadLocalUserSession.class); - private TestUserRegistrar userIdentityAuthenticator = new TestUserRegistrar(); - private Server server = mock(Server.class); - private OAuthCsrfVerifier csrfVerifier = mock(OAuthCsrfVerifier.class); - private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); - private TestUserSessionFactory userSessionFactory = TestUserSessionFactory.standalone(); - private OAuth2AuthenticationParameters oAuthParameters = mock(OAuth2AuthenticationParameters.class); - private HttpServletRequest request = mock(HttpServletRequest.class); - private HttpServletResponse response = mock(HttpServletResponse.class); - private HttpSession session = mock(HttpSession.class); - private OAuth2IdentityProvider identityProvider = mock(OAuth2IdentityProvider.class); - - private OAuth2ContextFactory underTest = new OAuth2ContextFactory(threadLocalUserSession, userIdentityAuthenticator, server, csrfVerifier, jwtHttpHandler, userSessionFactory, - oAuthParameters); - - @Before - public void setUp() throws Exception { - when(request.getSession()).thenReturn(session); - when(identityProvider.getKey()).thenReturn(PROVIDER_KEY); - when(identityProvider.getName()).thenReturn(PROVIDER_NAME); - } - - @Test - public void create_context() { - when(server.getPublicRootUrl()).thenReturn(SECURED_PUBLIC_ROOT_URL); - - OAuth2IdentityProvider.InitContext context = newInitContext(); - - assertThat(context.getRequest()).isEqualTo(request); - assertThat(context.getResponse()).isEqualTo(response); - assertThat(context.getCallbackUrl()).isEqualTo("https://mydomain.com/oauth2/callback/github"); - } - - @Test - public void generate_csrf_state() { - OAuth2IdentityProvider.InitContext context = newInitContext(); - - context.generateCsrfState(); - - verify(csrfVerifier).generateState(request, response); - } - - @Test - public void redirect_to() throws Exception { - OAuth2IdentityProvider.InitContext context = newInitContext(); - - context.redirectTo("/test"); - - verify(response).sendRedirect("/test"); - } - - @Test - public void create_callback() { - when(server.getPublicRootUrl()).thenReturn(SECURED_PUBLIC_ROOT_URL); - - OAuth2IdentityProvider.CallbackContext callback = newCallbackContext(); - - assertThat(callback.getRequest()).isEqualTo(request); - assertThat(callback.getResponse()).isEqualTo(response); - assertThat(callback.getCallbackUrl()).isEqualTo("https://mydomain.com/oauth2/callback/github"); - } - - @Test - public void authenticate() { - OAuth2IdentityProvider.CallbackContext callback = newCallbackContext(); - - callback.authenticate(USER_IDENTITY); - - assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue(); - verify(threadLocalUserSession).set(any(UserSession.class)); - ArgumentCaptor<UserDto> userArgumentCaptor = ArgumentCaptor.forClass(UserDto.class); - verify(jwtHttpHandler).generateToken(userArgumentCaptor.capture(), eq(request), eq(response)); - assertThat(userArgumentCaptor.getValue().getLogin()).isEqualTo(USER_IDENTITY.getLogin()); - assertThat(userArgumentCaptor.getValue().getExternalId()).isEqualTo(USER_IDENTITY.getProviderId()); - assertThat(userArgumentCaptor.getValue().getExternalLogin()).isEqualTo(USER_IDENTITY.getProviderLogin()); - 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(); - Set<String> organizationAlmIds = ImmutableSet.of("ABCD", "EFGH"); - - callback.authenticate(USER_IDENTITY, organizationAlmIds); - - assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getOrganizationAlmIds()).containsAll(organizationAlmIds); - } - - @Test - public void redirect_to_home() throws Exception { - when(server.getContextPath()).thenReturn(""); - when(oAuthParameters.getReturnTo(request)).thenReturn(Optional.empty()); - OAuth2IdentityProvider.CallbackContext callback = newCallbackContext(); - - callback.redirectToRequestedPage(); - - verify(response).sendRedirect("/"); - } - - @Test - public void redirect_to_home_with_context() throws Exception { - when(server.getContextPath()).thenReturn("/sonarqube"); - when(oAuthParameters.getReturnTo(request)).thenReturn(Optional.empty()); - OAuth2IdentityProvider.CallbackContext callback = newCallbackContext(); - - callback.redirectToRequestedPage(); - - verify(response).sendRedirect("/sonarqube/"); - } - - @Test - public void redirect_to_requested_page() throws Exception { - when(oAuthParameters.getReturnTo(request)).thenReturn(Optional.of("/settings")); - when(server.getContextPath()).thenReturn(""); - OAuth2IdentityProvider.CallbackContext callback = newCallbackContext(); - - callback.redirectToRequestedPage(); - - verify(response).sendRedirect("/settings"); - } - - @Test - public void redirect_to_requested_page_does_not_need_context() throws Exception { - when(oAuthParameters.getReturnTo(request)).thenReturn(Optional.of("/sonarqube/settings")); - when(server.getContextPath()).thenReturn("/other"); - OAuth2IdentityProvider.CallbackContext callback = newCallbackContext(); - - callback.redirectToRequestedPage(); - - verify(response).sendRedirect("/sonarqube/settings"); - } - - @Test - public void verify_csrf_state() { - OAuth2IdentityProvider.CallbackContext callback = newCallbackContext(); - - callback.verifyCsrfState(); - - verify(csrfVerifier).verifyState(request, response, identityProvider); - } - - @Test - public void delete_oauth2_parameters_during_redirection() { - when(oAuthParameters.getReturnTo(request)).thenReturn(Optional.of("/settings")); - when(server.getContextPath()).thenReturn(""); - OAuth2IdentityProvider.CallbackContext callback = newCallbackContext(); - - callback.redirectToRequestedPage(); - - verify(oAuthParameters).delete(eq(request), eq(response)); - } - - private OAuth2IdentityProvider.InitContext newInitContext() { - return underTest.newContext(request, response, identityProvider); - } - - private OAuth2IdentityProvider.CallbackContext newCallbackContext() { - return underTest.newCallback(request, response, identityProvider); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuthCsrfVerifierTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuthCsrfVerifierTest.java deleted file mode 100644 index d1165e1aea6..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/OAuthCsrfVerifierTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -import org.sonar.api.platform.Server; -import org.sonar.api.server.authentication.OAuth2IdentityProvider; -import org.sonar.server.authentication.event.AuthenticationEvent; - -import static org.apache.commons.codec.digest.DigestUtils.sha1Hex; -import static org.apache.commons.codec.digest.DigestUtils.sha256Hex; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException; - -public class OAuthCsrfVerifierTest { - private static final String PROVIDER_NAME = "provider name"; - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class); - - private OAuth2IdentityProvider identityProvider = mock(OAuth2IdentityProvider.class); - private Server server = mock(Server.class); - private HttpServletResponse response = mock(HttpServletResponse.class); - private HttpServletRequest request = mock(HttpServletRequest.class); - - private OAuthCsrfVerifier underTest = new OAuthCsrfVerifier(); - - @Before - public void setUp() throws Exception { - when(server.getContextPath()).thenReturn(""); - when(identityProvider.getName()).thenReturn(PROVIDER_NAME); - } - - @Test - public void generate_state() { - String state = underTest.generateState(request, response); - assertThat(state).isNotEmpty(); - - verify(response).addCookie(cookieArgumentCaptor.capture()); - - verifyCookie(cookieArgumentCaptor.getValue()); - } - - @Test - public void verify_state() { - String state = "state"; - when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("OAUTHSTATE", sha256Hex(state))}); - when(request.getParameter("aStateParameter")).thenReturn(state); - - underTest.verifyState(request, response, identityProvider, "aStateParameter"); - - verify(response).addCookie(cookieArgumentCaptor.capture()); - Cookie updatedCookie = cookieArgumentCaptor.getValue(); - assertThat(updatedCookie.getName()).isEqualTo("OAUTHSTATE"); - assertThat(updatedCookie.getValue()).isNull(); - assertThat(updatedCookie.getPath()).isEqualTo("/"); - assertThat(updatedCookie.getMaxAge()).isEqualTo(0); - } - - @Test - public void verify_state_using_default_state_parameter() { - String state = "state"; - when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("OAUTHSTATE", sha256Hex(state))}); - when(request.getParameter("state")).thenReturn(state); - - underTest.verifyState(request, response, identityProvider); - - verify(response).addCookie(cookieArgumentCaptor.capture()); - Cookie updatedCookie = cookieArgumentCaptor.getValue(); - assertThat(updatedCookie.getName()).isEqualTo("OAUTHSTATE"); - assertThat(updatedCookie.getValue()).isNull(); - assertThat(updatedCookie.getPath()).isEqualTo("/"); - assertThat(updatedCookie.getMaxAge()).isEqualTo(0); - } - - @Test - public void fail_with_AuthenticationException_when_state_cookie_is_not_the_same_as_state_parameter() { - when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("OAUTHSTATE", sha1Hex("state"))}); - when(request.getParameter("state")).thenReturn("other value"); - - thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin().andNoPublicMessage()); - thrown.expectMessage("CSRF state value is invalid"); - underTest.verifyState(request, response, identityProvider); - } - - @Test - public void fail_with_AuthenticationException_when_state_cookie_is_null() { - when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("OAUTHSTATE", null)}); - when(request.getParameter("state")).thenReturn("state"); - - thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin().andNoPublicMessage()); - thrown.expectMessage("CSRF state value is invalid"); - underTest.verifyState(request, response, identityProvider); - } - - @Test - public void fail_with_AuthenticationException_when_state_parameter_is_empty() { - when(request.getCookies()).thenReturn(new Cookie[] {new Cookie("OAUTHSTATE", sha1Hex("state"))}); - when(request.getParameter("state")).thenReturn(""); - - thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin().andNoPublicMessage()); - thrown.expectMessage("CSRF state value is invalid"); - underTest.verifyState(request, response, identityProvider); - } - - @Test - public void fail_with_AuthenticationException_when_cookie_is_missing() { - when(request.getCookies()).thenReturn(new Cookie[] {}); - - thrown.expect(authenticationException().from(AuthenticationEvent.Source.oauth2(identityProvider)).withoutLogin().andNoPublicMessage()); - thrown.expectMessage("Cookie 'OAUTHSTATE' is missing"); - underTest.verifyState(request, response, identityProvider); - } - - private void verifyCookie(Cookie cookie) { - assertThat(cookie.getName()).isEqualTo("OAUTHSTATE"); - assertThat(cookie.getValue()).isNotEmpty(); - assertThat(cookie.getPath()).isEqualTo("/"); - assertThat(cookie.isHttpOnly()).isTrue(); - assertThat(cookie.getMaxAge()).isEqualTo(-1); - assertThat(cookie.getSecure()).isFalse(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/RequestAuthenticatorImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/RequestAuthenticatorImplTest.java deleted file mode 100644 index be9a12754db..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/RequestAuthenticatorImplTest.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Test; -import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.event.AuthenticationEvent; -import org.sonar.server.authentication.event.AuthenticationException; -import org.sonar.server.tester.AnonymousMockUserSession; -import org.sonar.server.tester.MockUserSession; -import org.sonar.server.user.UserSession; -import org.sonar.server.user.UserSessionFactory; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.sonar.db.user.UserTesting.newUserDto; - -public class RequestAuthenticatorImplTest { - - private static final UserDto A_USER = newUserDto(); - - private HttpServletRequest request = mock(HttpServletRequest.class); - private HttpServletResponse response = mock(HttpServletResponse.class); - private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); - private BasicAuthentication basicAuthentication = mock(BasicAuthentication.class); - private HttpHeadersAuthentication httpHeadersAuthentication = mock(HttpHeadersAuthentication.class); - private UserSessionFactory sessionFactory = mock(UserSessionFactory.class); - private CustomAuthentication customAuthentication1 = mock(CustomAuthentication.class); - private CustomAuthentication customAuthentication2 = mock(CustomAuthentication.class); - private RequestAuthenticator underTest = new RequestAuthenticatorImpl(jwtHttpHandler, basicAuthentication, httpHeadersAuthentication, sessionFactory, - new CustomAuthentication[]{customAuthentication1, customAuthentication2}); - - @Before - public void setUp() throws Exception { - when(sessionFactory.create(A_USER)).thenReturn(new MockUserSession(A_USER)); - when(sessionFactory.createAnonymous()).thenReturn(new AnonymousMockUserSession()); - } - - @Test - public void authenticate_from_jwt_token() { - when(httpHeadersAuthentication.authenticate(request, response)).thenReturn(Optional.empty()); - when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.of(A_USER)); - - assertThat(underTest.authenticate(request, response).getUuid()).isEqualTo(A_USER.getUuid()); - verify(response, never()).setStatus(anyInt()); - } - - @Test - public void authenticate_from_basic_header() { - when(basicAuthentication.authenticate(request)).thenReturn(Optional.of(A_USER)); - when(httpHeadersAuthentication.authenticate(request, response)).thenReturn(Optional.empty()); - when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); - - assertThat(underTest.authenticate(request, response).getUuid()).isEqualTo(A_USER.getUuid()); - - verify(jwtHttpHandler).validateToken(request, response); - verify(basicAuthentication).authenticate(request); - verify(response, never()).setStatus(anyInt()); - } - - @Test - public void authenticate_from_sso() { - when(httpHeadersAuthentication.authenticate(request, response)).thenReturn(Optional.of(A_USER)); - when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); - - assertThat(underTest.authenticate(request, response).getUuid()).isEqualTo(A_USER.getUuid()); - - verify(httpHeadersAuthentication).authenticate(request, response); - verify(jwtHttpHandler, never()).validateToken(request, response); - verify(response, never()).setStatus(anyInt()); - } - - @Test - public void return_empty_if_not_authenticated() { - when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); - when(httpHeadersAuthentication.authenticate(request, response)).thenReturn(Optional.empty()); - when(basicAuthentication.authenticate(request)).thenReturn(Optional.empty()); - - UserSession session = underTest.authenticate(request, response); - assertThat(session.isLoggedIn()).isFalse(); - assertThat(session.getUuid()).isNull(); - verify(response, never()).setStatus(anyInt()); - } - - @Test - public void delegate_to_CustomAuthentication() { - when(customAuthentication1.authenticate(request, response)).thenReturn(Optional.of(new MockUserSession("foo"))); - - UserSession session = underTest.authenticate(request, response); - - assertThat(session.getLogin()).isEqualTo("foo"); - } - - @Test - public void CustomAuthentication_has_priority_over_core_authentications() { - // use-case: both custom and core authentications check the HTTP header "Authorization". - // The custom authentication should be able to test the header because that the core authentication - // throws an exception. - when(customAuthentication1.authenticate(request, response)).thenReturn(Optional.of(new MockUserSession("foo"))); - when(basicAuthentication.authenticate(request)).thenThrow(AuthenticationException.newBuilder() - .setSource(AuthenticationEvent.Source.sso()) - .setMessage("message") - .build()); - - UserSession session = underTest.authenticate(request, response); - - assertThat(session.getLogin()).isEqualTo("foo"); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java deleted file mode 100644 index 9ba9f5c6f62..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.junit.Test; -import org.sonar.api.web.UserRole; -import org.sonar.db.permission.OrganizationPermission; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto; - -public class SafeModeUserSessionTest { - - private SafeModeUserSession underTest = new SafeModeUserSession(); - - @Test - public void session_is_anonymous() { - assertThat(underTest.getLogin()).isNull(); - assertThat(underTest.getUuid()).isNull(); - assertThat(underTest.isLoggedIn()).isFalse(); - assertThat(underTest.getName()).isNull(); - assertThat(underTest.getUserId()).isNull(); - assertThat(underTest.getGroups()).isEmpty(); - } - - @Test - public void session_has_no_permissions() { - assertThat(underTest.isRoot()).isFalse(); - assertThat(underTest.isSystemAdministrator()).isFalse(); - assertThat(underTest.hasPermissionImpl(OrganizationPermission.ADMINISTER, "foo")).isFalse(); - assertThat(underTest.hasProjectUuidPermission(UserRole.USER, "foo")).isFalse(); - assertThat(underTest.hasMembership(newOrganizationDto())).isFalse(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/TestIdentityProvider.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/TestIdentityProvider.java deleted file mode 100644 index fdc950b673c..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/TestIdentityProvider.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.sonar.api.server.authentication.Display; -import org.sonar.api.server.authentication.IdentityProvider; - -public class TestIdentityProvider implements IdentityProvider { - - private String key; - private String name; - private Display display; - private boolean enabled; - private boolean allowsUsersToSignUp; - - @Override - public String getKey() { - return key; - } - - public TestIdentityProvider setKey(String key) { - this.key = key; - return this; - } - - @Override - public String getName() { - return name; - } - - public TestIdentityProvider setName(String name) { - this.name = name; - return this; - } - - @Override - public Display getDisplay() { - return display; - } - - public TestIdentityProvider setDisplay(Display display) { - this.display = display; - return this; - } - - @Override - public boolean isEnabled() { - return enabled; - } - - public TestIdentityProvider setEnabled(boolean enabled) { - this.enabled = enabled; - return this; - } - - @Override - public boolean allowsUsersToSignUp() { - return allowsUsersToSignUp; - } - - public TestIdentityProvider setAllowsUsersToSignUp(boolean allowsUsersToSignUp) { - this.allowsUsersToSignUp = allowsUsersToSignUp; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (getClass() != o.getClass()) { - return false; - } - - TestIdentityProvider that = (TestIdentityProvider) o; - return key.equals(that.key); - } - - @Override - public int hashCode() { - return key.hashCode(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/TestUserRegistrar.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/TestUserRegistrar.java deleted file mode 100644 index 4244c842138..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/TestUserRegistrar.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserTesting; - -public class TestUserRegistrar implements UserRegistrar { - - private UserRegistration authenticatorParameters; - - @Override - public UserDto register(UserRegistration registration) { - this.authenticatorParameters = registration; - String providerId = registration.getUserIdentity().getProviderId(); - return UserTesting.newUserDto() - .setLocal(false) - .setLogin(registration.getUserIdentity().getLogin()) - .setExternalLogin(registration.getUserIdentity().getProviderLogin()) - .setExternalId(providerId == null ? registration.getUserIdentity().getProviderLogin() : providerId) - .setExternalIdentityProvider(registration.getProvider().getKey()); - } - - boolean isAuthenticated() { - return authenticatorParameters != null; - } - - UserRegistration getAuthenticatorParameters() { - return authenticatorParameters; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImplTest.java deleted file mode 100644 index f20e6c6bdf0..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserLastConnectionDatesUpdaterImplTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.api.impl.utils.TestSystem2; -import org.sonar.db.DbTester; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserTokenDto; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -public class UserLastConnectionDatesUpdaterImplTest { - - private static final long NOW = 10_000_000_000L; - private static final long ONE_MINUTE = 60_000L; - private static final long ONE_HOUR = ONE_MINUTE * 60L; - private static final long TWO_HOUR = ONE_HOUR * 2L; - - @Rule - public DbTester db = DbTester.create(); - - private System2 system2 = new TestSystem2().setNow(NOW); - - private UserLastConnectionDatesUpdaterImpl underTest = new UserLastConnectionDatesUpdaterImpl(db.getDbClient(), system2); - - @Test - public void update_last_connection_date_from_user_when_last_connection_was_more_than_one_hour() { - UserDto user = db.users().insertUser(); - db.users().updateLastConnectionDate(user, NOW - TWO_HOUR); - - underTest.updateLastConnectionDateIfNeeded(user); - - UserDto userReloaded = db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()); - assertThat(userReloaded.getLastConnectionDate()).isEqualTo(NOW); - } - - @Test - public void update_last_connection_date_from_user_when_no_last_connection_date() { - UserDto user = db.users().insertUser(); - - underTest.updateLastConnectionDateIfNeeded(user); - - UserDto userReloaded = db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()); - assertThat(userReloaded.getLastConnectionDate()).isEqualTo(NOW); - } - - @Test - public void do_not_update_when_last_connection_from_user_was_less_than_one_hour() { - UserDto user = db.users().insertUser(); - db.users().updateLastConnectionDate(user, NOW - ONE_MINUTE); - - underTest.updateLastConnectionDateIfNeeded(user); - - UserDto userReloaded = db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()); - assertThat(userReloaded.getLastConnectionDate()).isEqualTo(NOW - ONE_MINUTE); - } - - @Test - public void update_last_connection_date_from_user_token_when_last_connection_was_more_than_one_hour() { - UserDto user = db.users().insertUser(); - UserTokenDto userToken = db.users().insertToken(user); - db.getDbClient().userTokenDao().update(db.getSession(), userToken.setLastConnectionDate(NOW - TWO_HOUR)); - db.commit(); - - underTest.updateLastConnectionDateIfNeeded(userToken); - - UserTokenDto userTokenReloaded = db.getDbClient().userTokenDao().selectByTokenHash(db.getSession(), userToken.getTokenHash()); - assertThat(userTokenReloaded.getLastConnectionDate()).isEqualTo(NOW); - } - - @Test - public void update_last_connection_date_from_user_token_when_no_last_connection_date() { - UserDto user = db.users().insertUser(); - UserTokenDto userToken = db.users().insertToken(user); - - underTest.updateLastConnectionDateIfNeeded(userToken); - - UserTokenDto userTokenReloaded = db.getDbClient().userTokenDao().selectByTokenHash(db.getSession(), userToken.getTokenHash()); - assertThat(userTokenReloaded.getLastConnectionDate()).isEqualTo(NOW); - } - - @Test - public void do_not_update_when_last_connection_from_user_token_was_less_than_one_hour() { - UserDto user = db.users().insertUser(); - UserTokenDto userToken = db.users().insertToken(user); - db.getDbClient().userTokenDao().update(db.getSession(), userToken.setLastConnectionDate(NOW - ONE_MINUTE)); - db.commit(); - - underTest.updateLastConnectionDateIfNeeded(userToken); - - UserTokenDto userTokenReloaded = db.getDbClient().userTokenDao().selectByTokenHash(db.getSession(), userToken.getTokenHash()); - assertThat(userTokenReloaded.getLastConnectionDate()).isEqualTo(NOW - ONE_MINUTE); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserRegistrarImplOrgMembershipSyncTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserRegistrarImplOrgMembershipSyncTest.java deleted file mode 100644 index c43fdb7f616..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserRegistrarImplOrgMembershipSyncTest.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import com.google.common.collect.ImmutableSet; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.ResourceTypes; -import org.sonar.api.server.authentication.UserIdentity; -import org.sonar.api.utils.System2; -import org.sonar.db.DbTester; -import org.sonar.db.alm.AlmAppInstallDto; -import org.sonar.db.component.ResourceTypesRule; -import org.sonar.db.organization.OrganizationDto; -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.es.EsTester; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.MemberUpdater; -import org.sonar.server.organization.OrganizationUpdater; -import org.sonar.server.organization.TestDefaultOrganizationProvider; -import org.sonar.server.organization.TestOrganizationFlags; -import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionServiceImpl; -import org.sonar.server.user.NewUserNotifier; -import org.sonar.server.user.UserUpdater; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static org.mockito.Mockito.mock; -import static org.sonar.db.alm.ALM.BITBUCKETCLOUD; -import static org.sonar.db.alm.ALM.GITHUB; -import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC; - -public class UserRegistrarImplOrgMembershipSyncTest { - - private System2 system2 = new AlwaysIncreasingSystem2(); - - private static String USER_LOGIN = "github-johndoo"; - - private static UserIdentity USER_IDENTITY = UserIdentity.builder() - .setProviderId("ABCD") - .setProviderLogin("johndoo") - .setLogin(USER_LOGIN) - .setName("John") - .setEmail("john@email.com") - .build(); - - private static TestIdentityProvider GITHUB_PROVIDER = new TestIdentityProvider() - .setKey("github") - .setName("Github") - .setEnabled(true) - .setAllowsUsersToSignUp(true); - - private static TestIdentityProvider BITBUCKET_PROVIDER = new TestIdentityProvider() - .setKey("bitbucket") - .setName("Bitbucket") - .setEnabled(true) - .setAllowsUsersToSignUp(true); - - private MapSettings settings = new MapSettings(); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Rule - public DbTester db = DbTester.create(new AlwaysIncreasingSystem2()); - @Rule - public EsTester es = EsTester.create(); - private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client()); - private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); - private OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.class); - private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); - private CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient()); - private UserUpdater userUpdater = new UserUpdater( - system2, - mock(NewUserNotifier.class), - db.getDbClient(), - userIndexer, - organizationFlags, - defaultOrganizationProvider, - new DefaultGroupFinder(db.getDbClient()), - settings.asConfig(), - localAuthentication); - - private ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT); - private PermissionService permissionService = new PermissionServiceImpl(resourceTypes); - private DefaultGroupFinder defaultGroupFinder = new DefaultGroupFinder(db.getDbClient()); - - private UserRegistrarImpl underTest = new UserRegistrarImpl(db.getDbClient(), userUpdater, defaultOrganizationProvider, organizationFlags, - defaultGroupFinder, new MemberUpdater(db.getDbClient(), defaultGroupFinder, userIndexer)); - - @Test - public void authenticate_new_github_user_syncs_organization() { - organizationFlags.setEnabled(true); - OrganizationDto organization = db.organizations().insert(); - db.users().insertDefaultGroup(organization, "Members"); - AlmAppInstallDto gitHubInstall = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization, gitHubInstall, true); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(GITHUB_PROVIDER) - .setSource(Source.realm(BASIC, GITHUB_PROVIDER.getName())) - .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW) - .setOrganizationAlmIds(ImmutableSet.of(gitHubInstall.getOrganizationAlmId())) - .build()); - - UserDto user = db.users().selectUserByLogin(USER_LOGIN).get(); - db.organizations().assertUserIsMemberOfOrganization(organization, user); - } - - @Test - public void authenticate_new_github_user_does_not_sync_organization_when_no_org_alm_ids_provided() { - organizationFlags.setEnabled(true); - OrganizationDto organization = db.organizations().insert(); - db.users().insertDefaultGroup(organization, "Members"); - AlmAppInstallDto gitHubInstall = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization, gitHubInstall, true); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(GITHUB_PROVIDER) - .setSource(Source.realm(BASIC, GITHUB_PROVIDER.getName())) - .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW) - .setOrganizationAlmIds(null) - .build()); - - UserDto user = db.users().selectUserByLogin(USER_LOGIN).get(); - db.organizations().assertUserIsNotMemberOfOrganization(organization, user); - } - - @Test - public void authenticate_new_bitbucket_user_does_not_sync_organization() { - organizationFlags.setEnabled(true); - OrganizationDto organization = db.organizations().insert(); - db.users().insertDefaultGroup(organization, "Members"); - AlmAppInstallDto gitHubInstall = db.alm().insertAlmAppInstall(a -> a.setAlm(BITBUCKETCLOUD)); - db.alm().insertOrganizationAlmBinding(organization, gitHubInstall, true); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(BITBUCKET_PROVIDER) - .setSource(Source.realm(BASIC, BITBUCKET_PROVIDER.getName())) - .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW) - .setOrganizationAlmIds(ImmutableSet.of(gitHubInstall.getOrganizationAlmId())) - .build()); - - UserDto user = db.users().selectUserByLogin(USER_LOGIN).get(); - db.organizations().assertUserIsNotMemberOfOrganization(organization, user); - } - - @Test - public void authenticate_new_user_using_unknown_alm_does_not_sync_organization() { - organizationFlags.setEnabled(true); - OrganizationDto organization = db.organizations().insert(); - db.users().insertDefaultGroup(organization, "Members"); - AlmAppInstallDto almAppInstall = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization, almAppInstall, true); - TestIdentityProvider identityProvider = new TestIdentityProvider() - .setKey("unknown") - .setName("unknown") - .setEnabled(true) - .setAllowsUsersToSignUp(true); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(identityProvider) - .setSource(Source.realm(BASIC, identityProvider.getName())) - .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW) - .setOrganizationAlmIds(ImmutableSet.of(almAppInstall.getOrganizationAlmId())) - .build()); - - UserDto user = db.users().selectUserByLogin(USER_LOGIN).get(); - db.organizations().assertUserIsNotMemberOfOrganization(organization, user); - } - - @Test - public void authenticate_existing_github_user_does_not_sync_organization() { - organizationFlags.setEnabled(true); - OrganizationDto organization = db.organizations().insert(); - db.users().insertDefaultGroup(organization, "Members"); - AlmAppInstallDto gitHubInstall = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization, gitHubInstall, true); - UserDto user = db.users().insertUser(u -> u - .setLogin("Old login") - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalIdentityProvider(GITHUB_PROVIDER.getKey())); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(GITHUB_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setOrganizationAlmIds(ImmutableSet.of(gitHubInstall.getOrganizationAlmId())) - .build()); - - db.organizations().assertUserIsNotMemberOfOrganization(organization, user); - } - - @Test - public void authenticate_disabled_github_user_syncs_organization() { - organizationFlags.setEnabled(true); - OrganizationDto organization = db.organizations().insert(); - db.users().insertDefaultGroup(organization, "Members"); - AlmAppInstallDto gitHubInstall = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization, gitHubInstall, true); - UserDto user = db.users().insertDisabledUser(u -> u.setLogin(USER_LOGIN)); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(GITHUB_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .setOrganizationAlmIds(ImmutableSet.of(gitHubInstall.getOrganizationAlmId())) - .build()); - - db.organizations().assertUserIsMemberOfOrganization(organization, user); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java deleted file mode 100644 index 4673a5fce22..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserRegistrarImplTest.java +++ /dev/null @@ -1,791 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import java.util.Optional; -import java.util.stream.Collectors; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; -import org.sonar.api.server.authentication.UserIdentity; -import org.sonar.api.utils.System2; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbTester; -import org.sonar.db.organization.OrganizationDto; -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.exception.EmailAlreadyExistsRedirectionException; -import org.sonar.server.es.EsTester; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.MemberUpdater; -import org.sonar.server.organization.OrganizationUpdater; -import org.sonar.server.organization.TestDefaultOrganizationProvider; -import org.sonar.server.organization.TestOrganizationFlags; -import org.sonar.server.user.NewUserNotifier; -import org.sonar.server.user.UserUpdater; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static com.google.common.collect.Sets.newHashSet; -import static java.util.Arrays.stream; -import static org.assertj.core.api.Assertions.assertThat; -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; - -public class UserRegistrarImplTest { - - private System2 system2 = new AlwaysIncreasingSystem2(); - - private static String USER_LOGIN = "github-johndoo"; - - private static UserIdentity USER_IDENTITY = UserIdentity.builder() - .setProviderId("ABCD") - .setProviderLogin("johndoo") - .setLogin(USER_LOGIN) - .setName("John") - .setEmail("john@email.com") - .build(); - - private static TestIdentityProvider IDENTITY_PROVIDER = new TestIdentityProvider() - .setKey("github") - .setName("name of github") - .setEnabled(true) - .setAllowsUsersToSignUp(true); - - private MapSettings settings = new MapSettings(); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Rule - public DbTester db = DbTester.create(new AlwaysIncreasingSystem2()); - @Rule - public EsTester es = EsTester.create(); - private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client()); - private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); - private OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.class); - private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); - private CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient()); - private UserUpdater userUpdater = new UserUpdater( - system2, - mock(NewUserNotifier.class), - db.getDbClient(), - userIndexer, - organizationFlags, - defaultOrganizationProvider, - new DefaultGroupFinder(db.getDbClient()), - settings.asConfig(), - localAuthentication); - - private DefaultGroupFinder defaultGroupFinder = new DefaultGroupFinder(db.getDbClient()); - - private UserRegistrarImpl underTest = new UserRegistrarImpl(db.getDbClient(), userUpdater, defaultOrganizationProvider, organizationFlags, - defaultGroupFinder, new MemberUpdater(db.getDbClient(), defaultGroupFinder, userIndexer)); - - @Test - public void authenticate_new_user() { - organizationFlags.setEnabled(true); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.realm(BASIC, IDENTITY_PROVIDER.getName())) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - UserDto user = db.users().selectUserByLogin(USER_LOGIN).get(); - assertThat(user).isNotNull(); - assertThat(user.isActive()).isTrue(); - assertThat(user.getName()).isEqualTo("John"); - assertThat(user.getEmail()).isEqualTo("john@email.com"); - assertThat(user.getExternalLogin()).isEqualTo("johndoo"); - assertThat(user.getExternalIdentityProvider()).isEqualTo("github"); - assertThat(user.getExternalId()).isEqualTo("ABCD"); - assertThat(user.isRoot()).isFalse(); - checkGroupMembership(user); - } - - @Test - public void authenticate_new_user_generate_login_when_no_login_provided() { - organizationFlags.setEnabled(true); - - underTest.register(UserRegistration.builder() - .setUserIdentity(UserIdentity.builder() - .setProviderId("ABCD") - .setProviderLogin("johndoo") - .setName("John Doe") - .setEmail("john@email.com") - .build()) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.realm(BASIC, IDENTITY_PROVIDER.getName())) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - UserDto user = db.getDbClient().userDao().selectByEmail(db.getSession(), "john@email.com").get(0); - assertThat(user).isNotNull(); - assertThat(user.isActive()).isTrue(); - assertThat(user.getLogin()).isNotEqualTo("John Doe").startsWith("john-doe"); - assertThat(user.getEmail()).isEqualTo("john@email.com"); - assertThat(user.getExternalLogin()).isEqualTo("johndoo"); - assertThat(user.getExternalIdentityProvider()).isEqualTo("github"); - assertThat(user.getExternalId()).isEqualTo("ABCD"); - } - - @Test - public void authenticate_new_user_with_groups() { - organizationFlags.setEnabled(true); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); - - authenticate(USER_LOGIN, "group1", "group2", "group3"); - - Optional<UserDto> user = db.users().selectUserByLogin(USER_LOGIN); - checkGroupMembership(user.get(), group1, group2); - } - - @Test - public void authenticate_new_user_and_force_default_group_when_organizations_are_disabled() { - organizationFlags.setEnabled(false); - UserDto user = db.users().insertUser(); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto defaultGroup = insertDefaultGroup(); - db.users().insertMember(group1, user); - db.users().insertMember(defaultGroup, user); - - authenticate(user.getLogin(), "group1"); - - checkGroupMembership(user, group1, defaultGroup); - } - - @Test - public void does_not_force_default_group_when_authenticating_new_user_if_organizations_are_enabled() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto defaultGroup = insertDefaultGroup(); - db.users().insertMember(group1, user); - db.users().insertMember(defaultGroup, user); - - authenticate(user.getLogin(), "group1"); - - checkGroupMembership(user, group1); - } - - @Test - public void authenticate_new_user_sets_onboarded_flag_to_false_when_onboarding_setting_is_set_to_true() { - organizationFlags.setEnabled(true); - settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS.getKey(), true); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - assertThat(db.users().selectUserByLogin(USER_LOGIN).get().isOnboarded()).isFalse(); - } - - @Test - public void authenticate_new_user_sets_onboarded_flag_to_true_when_onboarding_setting_is_set_to_false() { - organizationFlags.setEnabled(true); - settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS.getKey(), false); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - assertThat(db.users().selectUserByLogin(USER_LOGIN).get().isOnboarded()).isTrue(); - } - - @Test - public void external_id_is_set_to_provider_login_when_null() { - organizationFlags.setEnabled(true); - UserIdentity newUser = UserIdentity.builder() - .setProviderId(null) - .setLogin("john") - .setProviderLogin("johndoo") - .setName("JOhn") - .build(); - - underTest.register(UserRegistration.builder() - .setUserIdentity(newUser) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - assertThat(db.users().selectUserByLogin(newUser.getLogin()).get()) - .extracting(UserDto::getLogin, UserDto::getExternalId, UserDto::getExternalLogin) - .contains("john", "johndoo", "johndoo"); - } - - @Test - public void authenticate_new_user_update_existing_user_email_when_strategy_is_ALLOW() { - organizationFlags.setEnabled(true); - UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); - UserIdentity newUser = UserIdentity.builder() - .setProviderLogin("johndoo") - .setLogin("new_login") - .setName(existingUser.getName()) - .setEmail(existingUser.getEmail()) - .build(); - - underTest.register(UserRegistration.builder() - .setUserIdentity(newUser) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW) - .build()); - - UserDto newUserReloaded = db.users().selectUserByLogin(newUser.getLogin()).get(); - assertThat(newUserReloaded.getEmail()).isEqualTo(existingUser.getEmail()); - UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get(); - assertThat(existingUserReloaded.getEmail()).isNull(); - } - - @Test - public void throw_EmailAlreadyExistException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_WARN() { - organizationFlags.setEnabled(true); - UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); - UserIdentity newUser = UserIdentity.builder() - .setProviderLogin("johndoo") - .setLogin("new_login") - .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 throw_AuthenticationException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_FORBID() { - db.users().insertUser(u -> u.setEmail("john@email.com")); - Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName()); - - 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.")); - expectedException.expectMessage("Email 'john@email.com' is already used"); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(source) - .setExistingEmailStrategy(FORBID) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - } - - @Test - public void throw_AuthenticationException_when_authenticating_new_user_and_email_already_exists_multiple_times() { - db.users().insertUser(u -> u.setEmail("john@email.com")); - db.users().insertUser(u -> u.setEmail("john@email.com")); - Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName()); - - 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.")); - expectedException.expectMessage("Email 'john@email.com' is already used"); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(source) - .setExistingEmailStrategy(FORBID) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - } - - @Test - public void fail_to_authenticate_new_user_when_allow_users_to_signup_is_false() { - TestIdentityProvider identityProvider = new TestIdentityProvider() - .setKey("github") - .setName("Github") - .setEnabled(true) - .setAllowsUsersToSignUp(false); - Source source = Source.realm(AuthenticationEvent.Method.FORM, identityProvider.getName()); - - expectedException.expect(authenticationException().from(source).withLogin(USER_IDENTITY.getProviderLogin()).andPublicMessage("'github' users are not allowed to sign up")); - expectedException.expectMessage("User signup disabled for provider 'github'"); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(identityProvider) - .setSource(source) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - } - - @Test - public void authenticate_and_update_existing_user_matching_login() { - db.users().insertUser(u -> u - .setLogin(USER_LOGIN) - .setName("Old name") - .setEmail("Old email") - .setExternalId("old id") - .setExternalLogin("old identity") - .setExternalIdentityProvider("old provide")); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - assertThat(db.users().selectUserByLogin(USER_LOGIN).get()) - .extracting(UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, UserDto::isActive) - .contains("John", "john@email.com", "ABCD", "johndoo", "github", true); - } - - @Test - public void authenticate_and_update_existing_user_matching_external_id() { - UserDto user = db.users().insertUser(u -> u - .setLogin("Old login") - .setName("Old name") - .setEmail("Old email") - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - assertThat(db.users().selectUserByLogin("Old login")).isNotPresent(); - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, - UserDto::isActive) - .contains(USER_LOGIN, "John", "john@email.com", "ABCD", "johndoo", "github", true); - } - - @Test - public void authenticate_and_update_existing_user_matching_external_login() { - UserDto user = db.users().insertUser(u -> u - .setLogin("Old login") - .setName("Old name") - .setEmail(USER_IDENTITY.getEmail()) - .setExternalId("Old id") - .setExternalLogin(USER_IDENTITY.getProviderLogin()) - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - assertThat(db.users().selectUserByLogin("Old login")).isNotPresent(); - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, - UserDto::isActive) - .contains(USER_LOGIN, "John", "john@email.com", "ABCD", "johndoo", "github", true); - } - - @Test - public void authenticate_existing_user_and_update_only_login() { - UserDto user = db.users().insertUser(u -> u - .setLogin("old login") - .setName(USER_IDENTITY.getName()) - .setEmail(USER_IDENTITY.getEmail()) - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - assertThat(db.users().selectUserByLogin("Old login")).isNotPresent(); - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, - UserDto::isActive) - .containsExactlyInAnyOrder(USER_LOGIN, USER_IDENTITY.getName(), USER_IDENTITY.getEmail(), USER_IDENTITY.getProviderId(), USER_IDENTITY.getProviderLogin(), - IDENTITY_PROVIDER.getKey(), - true); - } - - @Test - public void authenticate_existing_user_and_update_only_identity_provider_key() { - UserDto user = db.users().insertUser(u -> u - .setLogin(USER_LOGIN) - .setName(USER_IDENTITY.getName()) - .setEmail(USER_IDENTITY.getEmail()) - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin(USER_IDENTITY.getProviderLogin()) - .setExternalIdentityProvider("old identity provider")); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, - UserDto::isActive) - .containsExactlyInAnyOrder(USER_LOGIN, USER_IDENTITY.getName(), USER_IDENTITY.getEmail(), USER_IDENTITY.getProviderId(), USER_IDENTITY.getProviderLogin(), - IDENTITY_PROVIDER.getKey(), - true); - } - - @Test - public void authenticate_existing_user_matching_login_when_external_id_is_null() { - UserDto user = db.users().insertUser(u -> u - .setLogin(USER_LOGIN) - .setName("Old name") - .setEmail("Old email") - .setExternalId("Old id") - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - - underTest.register(UserRegistration.builder() - .setUserIdentity(UserIdentity.builder() - .setProviderId(null) - .setProviderLogin("johndoo") - .setLogin(USER_LOGIN) - .setName("John") - .setEmail("john@email.com") - .build()) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, - UserDto::isActive) - .contains(user.getLogin(), "John", "john@email.com", "johndoo", "johndoo", "github", true); - } - - @Test - public void authenticate_existing_user_when_login_is_not_provided() { - UserDto user = db.users().insertUser(u -> u.setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - - underTest.register(UserRegistration.builder() - .setUserIdentity(UserIdentity.builder() - .setProviderId(user.getExternalId()) - .setProviderLogin(user.getExternalLogin()) - // No login provided - .setName(user.getName()) - .setEmail(user.getEmail()) - .build()) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - // No new user is created - assertThat(db.countRowsOfTable(db.getSession(), "users")).isEqualTo(1); - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, - UserDto::isActive) - .contains(user.getLogin(), user.getName(), user.getEmail(), user.getExternalId(), user.getExternalLogin(), user.getExternalIdentityProvider(), true); - } - - @Test - public void authenticate_existing_user_with_login_update_and_strategy_is_ALLOW() { - UserDto user = db.users().insertUser(u -> u - .setLogin("Old login") - .setExternalId(USER_IDENTITY.getProviderId()) - .setExternalLogin("old identity") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid())) - .extracting(UserDto::getLogin, UserDto::getExternalLogin) - .contains(USER_LOGIN, USER_IDENTITY.getProviderLogin()); - } - - @Test - public void authenticate_existing_disabled_user() { - organizationFlags.setEnabled(true); - db.users().insertUser(u -> u - .setLogin(USER_LOGIN) - .setActive(false) - .setName("Old name") - .setEmail("Old email") - .setExternalId("old id") - .setExternalLogin("old identity") - .setExternalIdentityProvider("old provide")); - - underTest.register(UserRegistration.builder() - .setUserIdentity(USER_IDENTITY) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - UserDto userDto = db.users().selectUserByLogin(USER_LOGIN).get(); - assertThat(userDto.isActive()).isTrue(); - assertThat(userDto.getName()).isEqualTo("John"); - assertThat(userDto.getEmail()).isEqualTo("john@email.com"); - assertThat(userDto.getExternalId()).isEqualTo("ABCD"); - assertThat(userDto.getExternalLogin()).isEqualTo("johndoo"); - assertThat(userDto.getExternalIdentityProvider()).isEqualTo("github"); - assertThat(userDto.isRoot()).isFalse(); - } - - @Test - public void authenticate_existing_user_when_email_already_exists_and_strategy_is_ALLOW() { - organizationFlags.setEnabled(true); - UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); - UserDto currentUser = db.users().insertUser(u -> u.setEmail(null)); - UserIdentity userIdentity = UserIdentity.builder() - .setLogin(currentUser.getLogin()) - .setProviderLogin("johndoo") - .setName("John") - .setEmail("john@email.com") - .build(); - - underTest.register(UserRegistration.builder() - .setUserIdentity(userIdentity) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW) - .build()); - - UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get(); - assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com"); - UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get(); - assertThat(existingUserReloaded.getEmail()).isNull(); - } - - @Test - public void throw_EmailAlreadyExistException_when_authenticating_existing_user_when_email_already_exists_and_strategy_is_WARN() { - organizationFlags.setEnabled(true); - UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); - UserDto currentUser = db.users().insertUser(u -> u.setEmail(null)); - UserIdentity userIdentity = UserIdentity.builder() - .setLogin(currentUser.getLogin()) - .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 throw_AuthenticationException_when_authenticating_existing_user_when_email_already_exists_and_strategy_is_FORBID() { - organizationFlags.setEnabled(true); - UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com")); - UserDto currentUser = db.users().insertUser(u -> u.setEmail(null)); - UserIdentity userIdentity = UserIdentity.builder() - .setLogin(currentUser.getLogin()) - .setProviderLogin("johndoo") - .setName("John") - .setEmail("john@email.com") - .build(); - - expectedException.expect(authenticationException().from(Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName())) - .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.")); - expectedException.expectMessage("Email 'john@email.com' is already used"); - - underTest.register(UserRegistration.builder() - .setUserIdentity(userIdentity) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName())) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - } - - @Test - public void does_not_fail_to_authenticate_user_when_email_has_not_changed_and_strategy_is_FORBID() { - organizationFlags.setEnabled(true); - UserDto currentUser = db.users().insertUser(u -> u.setEmail("john@email.com") - .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())); - UserIdentity userIdentity = UserIdentity.builder() - .setLogin(currentUser.getLogin()) - .setProviderId(currentUser.getExternalId()) - .setProviderLogin(currentUser.getExternalLogin()) - .setName("John") - .setEmail("john@email.com") - .build(); - - underTest.register(UserRegistration.builder() - .setUserIdentity(userIdentity) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get(); - assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com"); - } - - @Test - public void authenticate_existing_user_and_add_new_groups() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(newUserDto() - .setLogin(USER_LOGIN) - .setActive(true) - .setName("John")); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); - - authenticate(USER_LOGIN, "group1", "group2", "group3"); - - checkGroupMembership(user, group1, group2); - } - - @Test - public void authenticate_existing_user_and_remove_groups() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(newUserDto() - .setLogin(USER_LOGIN) - .setActive(true) - .setName("John")); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); - db.users().insertMember(group1, user); - db.users().insertMember(group2, user); - - authenticate(USER_LOGIN, "group1"); - - checkGroupMembership(user, group1); - } - - @Test - public void authenticate_existing_user_and_remove_all_groups_expect_default_when_organizations_are_disabled() { - organizationFlags.setEnabled(false); - UserDto user = db.users().insertUser(); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2"); - GroupDto defaultGroup = insertDefaultGroup(); - db.users().insertMember(group1, user); - db.users().insertMember(group2, user); - db.users().insertMember(defaultGroup, user); - - authenticate(user.getLogin()); - - checkGroupMembership(user, defaultGroup); - } - - @Test - public void does_not_force_default_group_when_authenticating_existing_user_when_organizations_are_enabled() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(); - GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1"); - GroupDto defaultGroup = insertDefaultGroup(); - db.users().insertMember(group1, user); - db.users().insertMember(defaultGroup, user); - - authenticate(user.getLogin(), "group1"); - - checkGroupMembership(user, group1); - } - - @Test - public void ignore_groups_on_non_default_organizations() { - organizationFlags.setEnabled(true); - OrganizationDto org = db.organizations().insert(); - UserDto user = db.users().insertUser(newUserDto() - .setLogin(USER_LOGIN) - .setActive(true) - .setName("John")); - String groupName = "a-group"; - GroupDto groupInDefaultOrg = db.users().insertGroup(db.getDefaultOrganization(), groupName); - GroupDto groupInOrg = db.users().insertGroup(org, groupName); - - // adding a group with the same name than in non-default organization - underTest.register(UserRegistration.builder() - .setUserIdentity(UserIdentity.builder() - .setProviderLogin("johndoo") - .setLogin(user.getLogin()) - .setName(user.getName()) - .setGroups(newHashSet(groupName)) - .build()) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - - checkGroupMembership(user, groupInDefaultOrg); - } - - private void authenticate(String login, String... groups) { - underTest.register(UserRegistration.builder() - .setUserIdentity(UserIdentity.builder() - .setProviderLogin("johndoo") - .setLogin(login) - .setName("John") - // No group - .setGroups(stream(groups).collect(MoreCollectors.toSet())) - .build()) - .setProvider(IDENTITY_PROVIDER) - .setSource(Source.local(BASIC)) - .setExistingEmailStrategy(ExistingEmailStrategy.FORBID) - .build()); - } - - private void checkGroupMembership(UserDto user, GroupDto... expectedGroups) { - assertThat(db.users().selectGroupIdsOfUser(user)).containsOnly(stream(expectedGroups).map(GroupDto::getId).collect(Collectors.toList()).toArray(new Integer[] {})); - } - - private GroupDto insertDefaultGroup() { - return db.users().insertDefaultGroup(db.getDefaultOrganization(), "sonar-users"); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java deleted file mode 100644 index cac1b7ceaf7..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.server.authentication.BaseIdentityProvider; -import org.sonar.api.utils.System2; -import org.sonar.db.DbTester; -import org.sonar.server.authentication.event.AuthenticationEvent; -import org.sonar.server.authentication.event.AuthenticationEvent.Method; -import org.sonar.server.authentication.event.AuthenticationEvent.Source; -import org.sonar.server.authentication.event.AuthenticationException; -import org.sonar.server.tester.AnonymousMockUserSession; -import org.sonar.server.tester.MockUserSession; -import org.sonar.server.user.ThreadLocalUserSession; -import org.sonar.server.user.UserSession; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -public class UserSessionInitializerTest { - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private ThreadLocalUserSession threadLocalSession = mock(ThreadLocalUserSession.class); - private HttpServletRequest request = mock(HttpServletRequest.class); - private HttpServletResponse response = mock(HttpServletResponse.class); - private RequestAuthenticator authenticator = mock(RequestAuthenticator.class); - private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - private MapSettings settings = new MapSettings(); - private ArgumentCaptor<Cookie> cookieArgumentCaptor = ArgumentCaptor.forClass(Cookie.class); - - private UserSessionInitializer underTest = new UserSessionInitializer(settings.asConfig(), threadLocalSession, authenticationEvent, authenticator); - - @Before - public void setUp() throws Exception { - when(request.getContextPath()).thenReturn(""); - when(request.getRequestURI()).thenReturn("/measures"); - } - - @Test - public void check_urls() { - assertPathIsNotIgnored("/"); - assertPathIsNotIgnored("/foo"); - assertPathIsNotIgnored("/api/server_id/show"); - - assertPathIsIgnored("/api/authentication/login"); - assertPathIsIgnored("/api/authentication/logout"); - assertPathIsIgnored("/api/authentication/validate"); - assertPathIsIgnored("/batch/index"); - assertPathIsIgnored("/batch/file"); - assertPathIsIgnored("/maintenance/index"); - assertPathIsIgnored("/setup/index"); - assertPathIsIgnored("/sessions/new"); - assertPathIsIgnored("/sessions/logout"); - assertPathIsIgnored("/sessions/unauthorized"); - assertPathIsIgnored("/oauth2/callback/github"); - assertPathIsIgnored("/oauth2/callback/foo"); - assertPathIsIgnored("/api/system/db_migration_status"); - assertPathIsIgnored("/api/system/status"); - assertPathIsIgnored("/api/system/migrate_db"); - assertPathIsIgnored("/api/server/version"); - assertPathIsIgnored("/api/users/identity_providers"); - assertPathIsIgnored("/api/l10n/index"); - - // exlude passcode urls - assertPathIsIgnoredWithAnonymousAccess("/api/ce/info"); - assertPathIsIgnoredWithAnonymousAccess("/api/ce/pause"); - assertPathIsIgnoredWithAnonymousAccess("/api/ce/resume"); - assertPathIsIgnoredWithAnonymousAccess("/api/system/health"); - - // exclude static resources - assertPathIsIgnored("/css/style.css"); - assertPathIsIgnored("/images/logo.png"); - assertPathIsIgnored("/js/jquery.js"); - } - - @Test - public void return_code_401_when_not_authenticated_and_with_force_authentication() { - ArgumentCaptor<AuthenticationException> exceptionArgumentCaptor = ArgumentCaptor.forClass(AuthenticationException.class); - when(threadLocalSession.isLoggedIn()).thenReturn(false); - when(authenticator.authenticate(request, response)).thenReturn(new AnonymousMockUserSession()); - settings.setProperty("sonar.forceAuthentication", true); - - assertThat(underTest.initUserSession(request, response)).isTrue(); - - verifyZeroInteractions(response); - verify(authenticationEvent).loginFailure(eq(request), exceptionArgumentCaptor.capture()); - verifyZeroInteractions(threadLocalSession); - AuthenticationException authenticationException = exceptionArgumentCaptor.getValue(); - assertThat(authenticationException.getSource()).isEqualTo(Source.local(Method.BASIC)); - assertThat(authenticationException.getLogin()).isNull(); - assertThat(authenticationException.getMessage()).isEqualTo("User must be authenticated"); - assertThat(authenticationException.getPublicMessage()).isNull(); - } - - @Test - public void return_401_and_stop_on_ws() { - when(request.getRequestURI()).thenReturn("/api/issues"); - AuthenticationException authenticationException = AuthenticationException.newBuilder().setSource(Source.jwt()).setMessage("Token id hasn't been found").build(); - doThrow(authenticationException).when(authenticator).authenticate(request, response); - - assertThat(underTest.initUserSession(request, response)).isFalse(); - - verify(response).setStatus(401); - verify(authenticationEvent).loginFailure(request, authenticationException); - verifyZeroInteractions(threadLocalSession); - } - - @Test - public void return_401_and_stop_on_batch_ws() { - when(request.getRequestURI()).thenReturn("/batch/global"); - doThrow(AuthenticationException.newBuilder().setSource(Source.jwt()).setMessage("Token id hasn't been found").build()) - .when(authenticator).authenticate(request, response); - - assertThat(underTest.initUserSession(request, response)).isFalse(); - - verify(response).setStatus(401); - verifyZeroInteractions(threadLocalSession); - } - - @Test - public void return_to_session_unauthorized_when_error_on_from_external_provider() throws Exception { - doThrow(AuthenticationException.newBuilder().setSource(Source.external(newBasicIdentityProvider("failing"))).setPublicMessage("Token id hasn't been found").build()) - .when(authenticator).authenticate(request, response); - - assertThat(underTest.initUserSession(request, response)).isFalse(); - - verify(response).sendRedirect("/sessions/unauthorized"); - verify(response).addCookie(cookieArgumentCaptor.capture()); - Cookie cookie = cookieArgumentCaptor.getValue(); - assertThat(cookie.getName()).isEqualTo("AUTHENTICATION-ERROR"); - assertThat(cookie.getValue()).isEqualTo("Token%20id%20hasn%27t%20been%20found"); - assertThat(cookie.getPath()).isEqualTo("/"); - assertThat(cookie.isHttpOnly()).isFalse(); - assertThat(cookie.getMaxAge()).isEqualTo(300); - assertThat(cookie.getSecure()).isFalse(); - } - - @Test - public void return_to_session_unauthorized_when_error_on_from_external_provider_with_context_path() throws Exception { - when(request.getContextPath()).thenReturn("/sonarqube"); - doThrow(AuthenticationException.newBuilder().setSource(Source.external(newBasicIdentityProvider("failing"))).setPublicMessage("Token id hasn't been found").build()) - .when(authenticator).authenticate(request, response); - - assertThat(underTest.initUserSession(request, response)).isFalse(); - - verify(response).sendRedirect("/sonarqube/sessions/unauthorized"); - } - - private void assertPathIsIgnored(String path) { - when(request.getRequestURI()).thenReturn(path); - - assertThat(underTest.initUserSession(request, response)).isTrue(); - - verifyZeroInteractions(threadLocalSession, authenticator); - reset(threadLocalSession, authenticator); - } - - private void assertPathIsIgnoredWithAnonymousAccess(String path) { - when(request.getRequestURI()).thenReturn(path); - UserSession session = new AnonymousMockUserSession(); - when(authenticator.authenticate(request, response)).thenReturn(session); - - assertThat(underTest.initUserSession(request, response)).isTrue(); - - verify(threadLocalSession).set(session); - reset(threadLocalSession, authenticator); - } - - private void assertPathIsNotIgnored(String path) { - when(request.getRequestURI()).thenReturn(path); - UserSession session = new MockUserSession("foo"); - when(authenticator.authenticate(request, response)).thenReturn(session); - - assertThat(underTest.initUserSession(request, response)).isTrue(); - - verify(threadLocalSession).set(session); - reset(threadLocalSession, authenticator); - } - - private static BaseIdentityProvider newBasicIdentityProvider(String name) { - BaseIdentityProvider mock = mock(BaseIdentityProvider.class); - when(mock.getName()).thenReturn(name); - return mock; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventImplTest.java deleted file mode 100644 index 7ea7b9be47a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventImplTest.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.event; - -import com.google.common.base.Joiner; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.utils.log.LogTester; -import org.sonar.api.utils.log.LoggerLevel; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; -import static org.sonar.server.authentication.event.AuthenticationEvent.Method; -import static org.sonar.server.authentication.event.AuthenticationEvent.Source; -import static org.sonar.server.authentication.event.AuthenticationException.newBuilder; - -public class AuthenticationEventImplTest { - private static final String LOGIN_129_CHARS = "012345678901234567890123456789012345678901234567890123456789" + - "012345678901234567890123456789012345678901234567890123456789012345678"; - - @Rule - public LogTester logTester = new LogTester(); - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private AuthenticationEventImpl underTest = new AuthenticationEventImpl(); - - @Before - public void setUp() throws Exception { - logTester.setLevel(LoggerLevel.DEBUG); - } - - @Test - public void login_success_fails_with_NPE_if_request_is_null() { - logTester.setLevel(LoggerLevel.INFO); - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("request can't be null"); - - underTest.loginSuccess(null, "login", Source.sso()); - } - - @Test - public void login_success_fails_with_NPE_if_source_is_null() { - logTester.setLevel(LoggerLevel.INFO); - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("source can't be null"); - - underTest.loginSuccess(mock(HttpServletRequest.class), "login", null); - } - - @Test - public void login_success_does_not_interact_with_request_if_log_level_is_above_DEBUG() { - HttpServletRequest request = mock(HttpServletRequest.class); - logTester.setLevel(LoggerLevel.INFO); - - underTest.loginSuccess(request, "login", Source.sso()); - - verifyZeroInteractions(request); - } - - @Test - public void login_success_creates_DEBUG_log_with_empty_login_if_login_argument_is_null() { - underTest.loginSuccess(mockRequest(), null, Source.sso()); - - verifyLog("login success [method|SSO][provider|SSO|sso][IP||][login|]"); - } - - @Test - public void login_success_creates_DEBUG_log_with_method_provider_and_login() { - underTest.loginSuccess(mockRequest(), "foo", Source.realm(Method.BASIC, "some provider name")); - - verifyLog("login success [method|BASIC][provider|REALM|some provider name][IP||][login|foo]"); - } - - @Test - public void login_success_prevents_log_flooding_on_login_starting_from_128_chars() { - underTest.loginSuccess(mockRequest(), LOGIN_129_CHARS, Source.realm(Method.BASIC, "some provider name")); - - verifyLog("login success [method|BASIC][provider|REALM|some provider name][IP||][login|012345678901234567890123456789012345678901234567890123456789" + - "01234567890123456789012345678901234567890123456789012345678901234567...(129)]"); - } - - @Test - public void login_success_logs_remote_ip_from_request() { - underTest.loginSuccess(mockRequest("1.2.3.4"), "foo", Source.realm(Method.EXTERNAL, "bar")); - - verifyLog("login success [method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|][login|foo]"); - } - - @Test - public void login_success_logs_X_Forwarded_For_header_from_request() { - HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5")); - underTest.loginSuccess(request, "foo", Source.realm(Method.EXTERNAL, "bar")); - - verifyLog("login success [method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|2.3.4.5][login|foo]"); - } - - @Test - public void login_success_logs_X_Forwarded_For_header_from_request_and_supports_multiple_headers() { - HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5", "6.5.4.3"), asList("9.5.6.7"), asList("6.3.2.4")); - underTest.loginSuccess(request, "foo", Source.realm(Method.EXTERNAL, "bar")); - - verifyLog("login success [method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|2.3.4.5,6.5.4.3,9.5.6.7,6.3.2.4][login|foo]"); - } - - @Test - public void login_failure_fails_with_NPE_if_request_is_null() { - logTester.setLevel(LoggerLevel.INFO); - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("request can't be null"); - - underTest.loginFailure(null, newBuilder().setSource(Source.sso()).build()); - } - - @Test - public void login_failure_fails_with_NPE_if_AuthenticationException_is_null() { - logTester.setLevel(LoggerLevel.INFO); - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("AuthenticationException can't be null"); - - underTest.loginFailure(mock(HttpServletRequest.class), null); - } - - @Test - public void login_failure_does_not_interact_with_arguments_if_log_level_is_above_DEBUG() { - HttpServletRequest request = mock(HttpServletRequest.class); - AuthenticationException exception = mock(AuthenticationException.class); - logTester.setLevel(LoggerLevel.INFO); - - underTest.loginFailure(request, exception); - - verifyZeroInteractions(request, exception); - } - - @Test - public void login_failure_creates_DEBUG_log_with_empty_login_if_AuthenticationException_has_no_login() { - AuthenticationException exception = newBuilder().setSource(Source.sso()).setMessage("message").build(); - underTest.loginFailure(mockRequest(), exception); - - verifyLog("login failure [cause|message][method|SSO][provider|SSO|sso][IP||][login|]"); - } - - @Test - public void login_failure_creates_DEBUG_log_with_empty_cause_if_AuthenticationException_has_no_message() { - AuthenticationException exception = newBuilder().setSource(Source.sso()).setLogin("FoO").build(); - underTest.loginFailure(mockRequest(), exception); - - verifyLog("login failure [cause|][method|SSO][provider|SSO|sso][IP||][login|FoO]"); - } - - @Test - public void login_failure_creates_DEBUG_log_with_method_provider_and_login() { - AuthenticationException exception = newBuilder() - .setSource(Source.realm(Method.BASIC, "some provider name")) - .setMessage("something got terribly wrong") - .setLogin("BaR") - .build(); - underTest.loginFailure(mockRequest(), exception); - - verifyLog("login failure [cause|something got terribly wrong][method|BASIC][provider|REALM|some provider name][IP||][login|BaR]"); - } - - @Test - public void login_failure_prevents_log_flooding_on_login_starting_from_128_chars() { - AuthenticationException exception = newBuilder() - .setSource(Source.realm(Method.BASIC, "some provider name")) - .setMessage("pop") - .setLogin(LOGIN_129_CHARS) - .build(); - underTest.loginFailure(mockRequest(), exception); - - verifyLog("login failure [cause|pop][method|BASIC][provider|REALM|some provider name][IP||][login|012345678901234567890123456789012345678901234567890123456789" + - "01234567890123456789012345678901234567890123456789012345678901234567...(129)]"); - } - - @Test - public void login_failure_logs_remote_ip_from_request() { - AuthenticationException exception = newBuilder() - .setSource(Source.realm(Method.EXTERNAL, "bar")) - .setMessage("Damn it!") - .setLogin("Baaad") - .build(); - underTest.loginFailure(mockRequest("1.2.3.4"), exception); - - verifyLog("login failure [cause|Damn it!][method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|][login|Baaad]"); - } - - @Test - public void login_failure_logs_X_Forwarded_For_header_from_request() { - AuthenticationException exception = newBuilder() - .setSource(Source.realm(Method.EXTERNAL, "bar")) - .setMessage("Hop la!") - .setLogin("foo") - .build(); - HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5")); - underTest.loginFailure(request, exception); - - verifyLog("login failure [cause|Hop la!][method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|2.3.4.5][login|foo]"); - } - - @Test - public void login_failure_logs_X_Forwarded_For_header_from_request_and_supports_multiple_headers() { - AuthenticationException exception = newBuilder() - .setSource(Source.realm(Method.EXTERNAL, "bar")) - .setMessage("Boom!") - .setLogin("foo") - .build(); - HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5", "6.5.4.3"), asList("9.5.6.7"), asList("6.3.2.4")); - underTest.loginFailure(request, exception); - - verifyLog("login failure [cause|Boom!][method|EXTERNAL][provider|REALM|bar][IP|1.2.3.4|2.3.4.5,6.5.4.3,9.5.6.7,6.3.2.4][login|foo]"); - } - - @Test - public void logout_success_fails_with_NPE_if_request_is_null() { - logTester.setLevel(LoggerLevel.INFO); - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("request can't be null"); - - underTest.logoutSuccess(null, "foo"); - } - - @Test - public void logout_success_does_not_interact_with_request_if_log_level_is_above_DEBUG() { - HttpServletRequest request = mock(HttpServletRequest.class); - logTester.setLevel(LoggerLevel.INFO); - - underTest.logoutSuccess(request, "foo"); - - verifyZeroInteractions(request); - } - - @Test - public void logout_success_creates_DEBUG_log_with_empty_login_if_login_argument_is_null() { - underTest.logoutSuccess(mockRequest(), null); - - verifyLog("logout success [IP||][login|]"); - } - - @Test - public void logout_success_creates_DEBUG_log_with_login() { - underTest.logoutSuccess(mockRequest(), "foo"); - - verifyLog("logout success [IP||][login|foo]"); - } - - @Test - public void logout_success_logs_remote_ip_from_request() { - underTest.logoutSuccess(mockRequest("1.2.3.4"), "foo"); - - verifyLog("logout success [IP|1.2.3.4|][login|foo]"); - } - - @Test - public void logout_success_logs_X_Forwarded_For_header_from_request() { - HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5")); - underTest.logoutSuccess(request, "foo"); - - verifyLog("logout success [IP|1.2.3.4|2.3.4.5][login|foo]"); - } - - @Test - public void logout_success_logs_X_Forwarded_For_header_from_request_and_supports_multiple_headers() { - HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5", "6.5.4.3"), asList("9.5.6.7"), asList("6.3.2.4")); - underTest.logoutSuccess(request, "foo"); - - verifyLog("logout success [IP|1.2.3.4|2.3.4.5,6.5.4.3,9.5.6.7,6.3.2.4][login|foo]"); - } - - @Test - public void logout_failure_with_NPE_if_request_is_null() { - logTester.setLevel(LoggerLevel.INFO); - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("request can't be null"); - - underTest.logoutFailure(null, "bad csrf"); - } - - @Test - public void login_fails_with_NPE_if_error_message_is_null() { - logTester.setLevel(LoggerLevel.INFO); - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("error message can't be null"); - - underTest.logoutFailure(mock(HttpServletRequest.class), null); - } - - @Test - public void logout_does_not_interact_with_request_if_log_level_is_above_DEBUG() { - HttpServletRequest request = mock(HttpServletRequest.class); - logTester.setLevel(LoggerLevel.INFO); - - underTest.logoutFailure(request, "bad csrf"); - - verifyZeroInteractions(request); - } - - @Test - public void logout_creates_DEBUG_log_with_error() { - underTest.logoutFailure(mockRequest(), "bad token"); - - verifyLog("logout failure [error|bad token][IP||]"); - } - - @Test - public void logout_logs_remote_ip_from_request() { - underTest.logoutFailure(mockRequest("1.2.3.4"), "bad token"); - - verifyLog("logout failure [error|bad token][IP|1.2.3.4|]"); - } - - @Test - public void logout_logs_X_Forwarded_For_header_from_request() { - HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5")); - underTest.logoutFailure(request, "bad token"); - - verifyLog("logout failure [error|bad token][IP|1.2.3.4|2.3.4.5]"); - } - - @Test - public void logout_logs_X_Forwarded_For_header_from_request_and_supports_multiple_headers() { - HttpServletRequest request = mockRequest("1.2.3.4", asList("2.3.4.5", "6.5.4.3"), asList("9.5.6.7"), asList("6.3.2.4")); - underTest.logoutFailure(request, "bad token"); - - verifyLog("logout failure [error|bad token][IP|1.2.3.4|2.3.4.5,6.5.4.3,9.5.6.7,6.3.2.4]"); - } - - private void verifyLog(String expected) { - assertThat(logTester.logs()).hasSize(1); - assertThat(logTester.logs(LoggerLevel.DEBUG)) - .containsOnly(expected); - } - - private static HttpServletRequest mockRequest() { - return mockRequest(""); - } - - private static HttpServletRequest mockRequest(String remoteAddr, List<String>... remoteIps) { - HttpServletRequest res = mock(HttpServletRequest.class); - when(res.getRemoteAddr()).thenReturn(remoteAddr); - when(res.getHeaders("X-Forwarded-For")) - .thenReturn(Collections.enumeration( - Arrays.stream(remoteIps) - .map(Joiner.on(",")::join) - .collect(Collectors.toList()))); - return res; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventSourceTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventSourceTest.java deleted file mode 100644 index 0bfac54e537..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationEventSourceTest.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.event; - -import java.io.Serializable; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.server.authentication.BaseIdentityProvider; -import org.sonar.api.server.authentication.OAuth2IdentityProvider; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.server.authentication.event.AuthenticationEvent.Method; -import static org.sonar.server.authentication.event.AuthenticationEvent.Provider; -import static org.sonar.server.authentication.event.AuthenticationEvent.Source; - -public class AuthenticationEventSourceTest { - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Test - public void local_fails_with_NPE_if_method_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("method can't be null"); - - Source.local(null); - } - - @Test - public void local_creates_source_instance_with_specified_method_and_hardcoded_provider_and_provider_name() { - Source underTest = Source.local(Method.BASIC_TOKEN); - - assertThat(underTest.getMethod()).isEqualTo(Method.BASIC_TOKEN); - assertThat(underTest.getProvider()).isEqualTo(Provider.LOCAL); - assertThat(underTest.getProviderName()).isEqualTo("local"); - } - - @Test - public void oauth2_fails_with_NPE_if_provider_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("identityProvider can't be null"); - - Source.oauth2(null); - } - - @Test - public void oauth2_fails_with_NPE_if_providerName_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("provider name can't be null"); - - Source.oauth2(newOauth2IdentityProvider(null)); - } - - @Test - public void oauth2_fails_with_IAE_if_providerName_is_empty() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("provider name can't be empty"); - - Source.oauth2(newOauth2IdentityProvider("")); - } - - @Test - public void oauth2_creates_source_instance_with_specified_provider_name_and_hardcoded_provider_and_method() { - Source underTest = Source.oauth2(newOauth2IdentityProvider("some name")); - - assertThat(underTest.getMethod()).isEqualTo(Method.OAUTH2); - assertThat(underTest.getProvider()).isEqualTo(Provider.EXTERNAL); - assertThat(underTest.getProviderName()).isEqualTo("some name"); - } - - @Test - public void realm_fails_with_NPE_if_method_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("method can't be null"); - - Source.realm(null, "name"); - } - - @Test - public void realm_fails_with_NPE_if_providerName_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("provider name can't be null"); - - Source.realm(Method.BASIC, null); - } - - @Test - public void realm_fails_with_IAE_if_providerName_is_empty() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("provider name can't be empty"); - - Source.realm(Method.BASIC, ""); - } - - @Test - public void realm_creates_source_instance_with_specified_method_and_provider_name_and_hardcoded_provider() { - Source underTest = Source.realm(Method.BASIC, "some name"); - - assertThat(underTest.getMethod()).isEqualTo(Method.BASIC); - assertThat(underTest.getProvider()).isEqualTo(Provider.REALM); - assertThat(underTest.getProviderName()).isEqualTo("some name"); - } - - @Test - public void sso_returns_source_instance_with_hardcoded_method_provider_and_providerName() { - Source underTest = Source.sso(); - - assertThat(underTest.getMethod()).isEqualTo(Method.SSO); - assertThat(underTest.getProvider()).isEqualTo(Provider.SSO); - assertThat(underTest.getProviderName()).isEqualTo("sso"); - - assertThat(underTest).isSameAs(Source.sso()); - } - - @Test - public void jwt_returns_source_instance_with_hardcoded_method_provider_and_providerName() { - Source underTest = Source.jwt(); - - assertThat(underTest.getMethod()).isEqualTo(Method.JWT); - assertThat(underTest.getProvider()).isEqualTo(Provider.JWT); - assertThat(underTest.getProviderName()).isEqualTo("jwt"); - - assertThat(underTest).isSameAs(Source.jwt()); - } - - @Test - public void external_fails_with_NPE_if_provider_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("identityProvider can't be null"); - - Source.external(null); - } - - @Test - public void external_fails_with_NPE_if_providerName_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("provider name can't be null"); - - Source.external(newBasicIdentityProvider(null)); - } - - @Test - public void external_fails_with_IAE_if_providerName_is_empty() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("provider name can't be empty"); - - Source.external(newBasicIdentityProvider("")); - } - - @Test - public void external_creates_source_instance_with_specified_provider_name_and_hardcoded_provider_and_method() { - Source underTest = Source.external(newBasicIdentityProvider("some name")); - - assertThat(underTest.getMethod()).isEqualTo(Method.EXTERNAL); - assertThat(underTest.getProvider()).isEqualTo(Provider.EXTERNAL); - assertThat(underTest.getProviderName()).isEqualTo("some name"); - } - - @Test - public void source_is_serializable() { - assertThat(Serializable.class.isAssignableFrom(Source.class)).isTrue(); - } - - @Test - public void toString_displays_all_fields() { - assertThat(Source.sso().toString()) - .isEqualTo("Source{method=SSO, provider=SSO, providerName='sso'}"); - assertThat(Source.oauth2(newOauth2IdentityProvider("bou")).toString()) - .isEqualTo("Source{method=OAUTH2, provider=EXTERNAL, providerName='bou'}"); - } - - @Test - public void source_implements_equals_on_all_fields() { - assertThat(Source.sso()).isEqualTo(Source.sso()); - assertThat(Source.sso()).isNotEqualTo(Source.jwt()); - assertThat(Source.jwt()).isEqualTo(Source.jwt()); - assertThat(Source.local(Method.BASIC)).isEqualTo(Source.local(Method.BASIC)); - assertThat(Source.local(Method.BASIC)).isNotEqualTo(Source.local(Method.BASIC_TOKEN)); - assertThat(Source.local(Method.BASIC)).isNotEqualTo(Source.sso()); - assertThat(Source.local(Method.BASIC)).isNotEqualTo(Source.jwt()); - assertThat(Source.local(Method.BASIC)).isNotEqualTo(Source.oauth2(newOauth2IdentityProvider("voo"))); - assertThat(Source.oauth2(newOauth2IdentityProvider("foo"))) - .isEqualTo(Source.oauth2(newOauth2IdentityProvider("foo"))); - assertThat(Source.oauth2(newOauth2IdentityProvider("foo"))) - .isNotEqualTo(Source.oauth2(newOauth2IdentityProvider("bar"))); - } - - private static OAuth2IdentityProvider newOauth2IdentityProvider(String name) { - OAuth2IdentityProvider mock = mock(OAuth2IdentityProvider.class); - when(mock.getName()).thenReturn(name); - return mock; - } - - private static BaseIdentityProvider newBasicIdentityProvider(String name) { - BaseIdentityProvider mock = mock(BaseIdentityProvider.class); - when(mock.getName()).thenReturn(name); - return mock; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationExceptionMatcher.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationExceptionMatcher.java deleted file mode 100644 index 8ad3da0070a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationExceptionMatcher.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.event; - -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.hamcrest.Description; -import org.hamcrest.TypeSafeMatcher; - -import static java.util.Objects.requireNonNull; -import static org.sonar.server.authentication.event.AuthenticationEvent.Source; - -/** - * Matcher for {@link AuthenticationException} to be used with {@link org.junit.rules.ExpectedException} JUnit Rule. - * - * <p> - * Usage: - * <pre> - * expectedException.expect(authenticationException().from(Source.local(Method.BASIC_TOKEN)).withLogin("foo").andNoPublicMessage()); - * </pre> - * </p> - */ -public class AuthenticationExceptionMatcher extends TypeSafeMatcher<Throwable> { - private final Source source; - @CheckForNull - private final String login; - @CheckForNull - private final String publicMessage; - - private AuthenticationExceptionMatcher(Source source, @Nullable String login, @Nullable String publicMessage) { - this.source = requireNonNull(source, "source can't be null"); - this.login = login; - this.publicMessage = publicMessage; - } - - public static Builder authenticationException() { - return new Builder(); - } - - public static class Builder { - private Source source; - private String login; - - public Builder from(Source source) { - this.source = checkSource(source); - return this; - } - - public Builder withLogin(String login) { - this.login = requireNonNull(login, "expected login can't be null"); - return this; - } - - public Builder withoutLogin() { - this.login = null; - return this; - } - - public AuthenticationExceptionMatcher andNoPublicMessage() { - return new AuthenticationExceptionMatcher(this.source, this.login, null); - } - - public AuthenticationExceptionMatcher andPublicMessage(String publicMessage){ - return new AuthenticationExceptionMatcher(this.source, this.login, requireNonNull(publicMessage)); - } - - private static Source checkSource(Source source) { - return requireNonNull(source, "expected source can't be null"); - } - } - - @Override - protected boolean matchesSafely(Throwable throwable) { - return check(throwable) == null; - } - - private String check(Throwable throwable) { - if (!throwable.getClass().isAssignableFrom(AuthenticationException.class)) { - return "exception is not a AuthenticationException"; - } - AuthenticationException authenticationException = (AuthenticationException) throwable; - if (!this.source.equals(authenticationException.getSource())) { - return "source is \"" + authenticationException.getSource() + "\""; - } - - String login = authenticationException.getLogin(); - if (this.login == null) { - if (login != null) { - return "login is \"" + login + "\""; - } - } else if (login == null) { - return "login is null"; - } else if (!this.login.equals(login)) { - return "login is \"" + login + "\""; - } - - String publicMessage = authenticationException.getPublicMessage(); - if (this.publicMessage == null) { - if (publicMessage != null) { - return "publicMessage is \"" + publicMessage + "\""; - } - } else if (publicMessage == null) { - return "publicMessage is null"; - } else if (!this.publicMessage.equals(publicMessage)) { - return "publicMessage is \"" + publicMessage + "\""; - } - - return null; - } - - @Override - public void describeTo(Description description) { - description.appendText("AuthenticationException with source ").appendText(source.toString()); - if (login == null) { - description.appendText(", no login"); - } else { - description.appendText(", login \"").appendText(login).appendText("\""); - } - if (publicMessage == null) { - description.appendText(" and no publicMessage"); - } else { - description.appendText(" and publicMessage \"").appendText(publicMessage).appendText("\""); - } - } - - @Override - protected void describeMismatchSafely(Throwable item, Description mismatchDescription) { - mismatchDescription.appendText(check(item)); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationExceptionTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationExceptionTest.java deleted file mode 100644 index 1472a4da905..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/event/AuthenticationExceptionTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.event; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.server.authentication.event.AuthenticationEvent.Source; - -import static org.assertj.core.api.Assertions.assertThat; - -public class AuthenticationExceptionTest { - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Test - public void build_fails_with_NPE_if_source_is_null() { - AuthenticationException.Builder builder = AuthenticationException.newBuilder() - .setLogin("login") - .setMessage("message"); - - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("source can't be null"); - - builder.build(); - } - - @Test - public void build_does_not_fail_if_login_is_null() { - AuthenticationException exception = AuthenticationException.newBuilder() - .setSource(Source.sso()) - .setMessage("message") - .build(); - - assertThat(exception.getSource()).isEqualTo(Source.sso()); - assertThat(exception.getMessage()).isEqualTo("message"); - assertThat(exception.getLogin()).isNull(); - } - - @Test - public void build_does_not_fail_if_message_is_null() { - AuthenticationException exception = AuthenticationException.newBuilder() - .setSource(Source.sso()) - .setLogin("login") - .build(); - - assertThat(exception.getSource()).isEqualTo(Source.sso()); - assertThat(exception.getMessage()).isNull(); - assertThat(exception.getLogin()).isEqualTo("login"); - } - - @Test - public void builder_set_methods_do_not_fail_if_login_is_null() { - AuthenticationException.newBuilder().setSource(null).setLogin(null).setMessage(null); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/exceptions/BadRequestExceptionTest.java b/server/sonar-server/src/test/java/org/sonar/server/exceptions/BadRequestExceptionTest.java deleted file mode 100644 index 0dc82c559f0..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/exceptions/BadRequestExceptionTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.exceptions; - -import java.util.Collections; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; - -public class BadRequestExceptionTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Test - public void text_error() { - BadRequestException exception = BadRequestException.create("error"); - assertThat(exception.getMessage()).isEqualTo("error"); - } - - @Test - public void create_exception_from_list() { - BadRequestException underTest = BadRequestException.create(asList("error1", "error2")); - - assertThat(underTest.errors()).containsOnly("error1", "error2"); - } - - @Test - public void create_exception_from_var_args() { - BadRequestException underTest = BadRequestException.create("error1", "error2"); - - assertThat(underTest.errors()).containsOnly("error1", "error2"); - } - - @Test - public void getMessage_return_first_error() { - BadRequestException underTest = BadRequestException.create(asList("error1", "error2")); - - assertThat(underTest.getMessage()).isEqualTo("error1"); - } - - @Test - public void fail_when_creating_exception_with_empty_list() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("At least one error message is required"); - - BadRequestException.create(Collections.emptyList()); - } - - @Test - public void fail_when_creating_exception_with_one_empty_element() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Message cannot be empty"); - - BadRequestException.create(asList("error", "")); - } - - @Test - public void fail_when_creating_exception_with_one_null_element() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Message cannot be empty"); - - BadRequestException.create(asList("error", null)); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/exceptions/MessageTest.java b/server/sonar-server/src/test/java/org/sonar/server/exceptions/MessageTest.java deleted file mode 100644 index f05bb55058a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/exceptions/MessageTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.exceptions; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class MessageTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Test - public void create_message() { - Message message = Message.of("key1 %s", "param1"); - assertThat(message.getMessage()).isEqualTo("key1 param1"); - } - - @Test - public void create_message_without_params() { - Message message = Message.of("key1"); - assertThat(message.getMessage()).isEqualTo("key1"); - } - - @Test - public void fail_when_message_is_null() { - expectedException.expect(IllegalArgumentException.class); - - Message.of(null); - } - - @Test - public void fail_when_message_is_empty() { - expectedException.expect(IllegalArgumentException.class); - - Message.of(""); - } - - @Test - public void test_equals_and_hashcode() { - Message message1 = Message.of("key1%s", "param1"); - Message message2 = Message.of("key2%s", "param2"); - Message message3 = Message.of("key1"); - Message message4 = Message.of("key1%s", "param2"); - Message sameAsMessage1 = Message.of("key1%s", "param1"); - - assertThat(message1).isEqualTo(message1); - assertThat(message1).isNotEqualTo(message2); - assertThat(message1).isNotEqualTo(message3); - assertThat(message1).isNotEqualTo(message4); - assertThat(message1).isEqualTo(sameAsMessage1); - assertThat(message1).isNotEqualTo(null); - assertThat(message1).isNotEqualTo(new Object()); - - assertThat(message1.hashCode()).isEqualTo(message1.hashCode()); - assertThat(message1.hashCode()).isNotEqualTo(message2.hashCode()); - assertThat(message1.hashCode()).isNotEqualTo(message3.hashCode()); - assertThat(message1.hashCode()).isNotEqualTo(message4.hashCode()); - assertThat(message1.hashCode()).isEqualTo(sameAsMessage1.hashCode()); - } - - @Test - public void to_string() { - assertThat(Message.of("key1 %s", "param1").toString()).isEqualTo("key1 param1"); - assertThat(Message.of("key1").toString()).isEqualTo("key1"); - assertThat(Message.of("key1", (Object[])null).toString()).isEqualTo("key1"); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/exceptions/ServerExceptionTest.java b/server/sonar-server/src/test/java/org/sonar/server/exceptions/ServerExceptionTest.java deleted file mode 100644 index ae2ce7846db..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/exceptions/ServerExceptionTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.exceptions; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ServerExceptionTest { - - @Test - public void should_create_exception_with_status() { - ServerException exception = new ServerException(400, "error!"); - assertThat(exception.httpCode()).isEqualTo(400); - } - - @Test - public void should_create_exception_with_status_and_message() { - ServerException exception = new ServerException(404, "Not found"); - assertThat(exception.httpCode()).isEqualTo(404); - assertThat(exception.getMessage()).isEqualTo("Not found"); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/language/LanguageTesting.java b/server/sonar-server/src/test/java/org/sonar/server/language/LanguageTesting.java deleted file mode 100644 index 644419c6c37..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/language/LanguageTesting.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.language; - -import com.google.common.collect.Collections2; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.resources.AbstractLanguage; -import org.sonar.api.resources.Language; -import org.sonar.api.resources.Languages; -import org.sonar.core.util.NonNullInputFunction; - -import java.util.Arrays; - -public class LanguageTesting { - - public static Language newLanguage(String key, String name, final String... prefixes) { - return new AbstractLanguage(key, name) { - @Override - public String[] getFileSuffixes() { - return prefixes; - } - }; - } - - public static Language newLanguage(String key) { - return newLanguage(key, StringUtils.capitalize(key)); - } - - public static Languages newLanguages(String... languageKeys) { - return new Languages(Collections2.transform(Arrays.asList(languageKeys), new NonNullInputFunction<String, Language>() { - @Override - protected Language doApply(String languageKey) { - return newLanguage(languageKey); - } - - }).toArray(new Language[0])); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationEnforcerTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationEnforcerTest.java deleted file mode 100644 index 3f145630f5f..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/DefaultOrganizationEnforcerTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import org.junit.Test; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -public class DefaultOrganizationEnforcerTest { - private DefaultOrganizationProvider defaultOrganizationProvider = mock(DefaultOrganizationProvider.class); - private DefaultOrganizationEnforcer underTest = new DefaultOrganizationEnforcer(defaultOrganizationProvider); - - @Test - public void start_calls_provider_get_method() { - underTest.start(); - - verify(defaultOrganizationProvider).get(); - verifyNoMoreInteractions(defaultOrganizationProvider); - } - - @Test - public void stop_does_nothing() { - underTest.stop(); - - verifyNoMoreInteractions(defaultOrganizationProvider); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/MemberUpdaterTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/MemberUpdaterTest.java deleted file mode 100644 index 309698e3c49..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/MemberUpdaterTest.java +++ /dev/null @@ -1,517 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import com.google.common.collect.ImmutableSet; -import java.util.HashSet; -import javax.annotation.Nullable; -import org.assertj.core.groups.Tuple; -import org.elasticsearch.action.search.SearchRequestBuilder; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.alm.AlmAppInstallDto; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.db.permission.template.PermissionTemplateDto; -import org.sonar.db.permission.template.PermissionTemplateUserDto; -import org.sonar.db.property.PropertyDto; -import org.sonar.db.property.PropertyQuery; -import org.sonar.db.qualityprofile.QProfileDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.es.EsTester; -import org.sonar.server.es.SearchOptions; -import org.sonar.server.user.index.UserDoc; -import org.sonar.server.user.index.UserIndex; -import org.sonar.server.user.index.UserIndexDefinition; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.user.index.UserQuery; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; -import static org.elasticsearch.index.query.QueryBuilders.boolQuery; -import static org.elasticsearch.index.query.QueryBuilders.termQuery; -import static org.sonar.api.CoreProperties.DEFAULT_ISSUE_ASSIGNEE; -import static org.sonar.api.web.UserRole.CODEVIEWER; -import static org.sonar.api.web.UserRole.USER; -import static org.sonar.db.alm.ALM.GITHUB; -import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; -import static org.sonar.db.permission.OrganizationPermission.SCAN; -import static org.sonar.server.user.index.UserIndexDefinition.FIELD_ORGANIZATION_UUIDS; -import static org.sonar.server.user.index.UserIndexDefinition.FIELD_UUID; - -public class MemberUpdaterTest { - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Rule - public EsTester es = EsTester.create(); - @Rule - public DbTester db = DbTester.create(); - - private DbClient dbClient = db.getDbClient(); - private UserIndex userIndex = new UserIndex(es.client(), System2.INSTANCE); - private UserIndexer userIndexer = new UserIndexer(dbClient, es.client()); - - private MemberUpdater underTest = new MemberUpdater(dbClient, new DefaultGroupFinder(dbClient), userIndexer); - - @Test - public void add_member_in_db_and_user_index() { - OrganizationDto organization = db.organizations().insert(); - db.users().insertDefaultGroup(organization, "Members"); - UserDto user = db.users().insertUser(); - - underTest.addMember(db.getSession(), organization, user); - - db.organizations().assertUserIsMemberOfOrganization(organization, user); - assertThat(userIndex.search(UserQuery.builder().build(), new SearchOptions()).getDocs()) - .extracting(UserDoc::login, UserDoc::organizationUuids) - .containsExactlyInAnyOrder(tuple(user.getLogin(), singletonList(organization.getUuid()))); - } - - @Test - public void does_not_fail_to_add_member_if_user_already_added_in_organization() { - OrganizationDto organization = db.organizations().insert(); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - UserDto user = db.users().insertUser(); - db.organizations().addMember(organization, user); - db.users().insertMember(defaultGroup, user); - db.organizations().assertUserIsMemberOfOrganization(organization, user); - - underTest.addMember(db.getSession(), organization, user); - - db.organizations().assertUserIsMemberOfOrganization(organization, user); - } - - @Test - public void add_member_fails_when_organization_has_no_default_group() { - OrganizationDto organization = db.organizations().insert(); - UserDto user = db.users().insertUser(); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage(format("Default group cannot be found on organization '%s'", organization.getUuid())); - - underTest.addMember(db.getSession(), organization, user); - } - - @Test - public void add_members_in_db_and_user_index() { - OrganizationDto organization = db.organizations().insert(); - db.users().insertDefaultGroup(organization, "Members"); - UserDto user1 = db.users().insertUser(); - UserDto user2 = db.users().insertUser(); - UserDto disableUser = db.users().insertDisabledUser(); - - underTest.addMembers(db.getSession(), organization, asList(user1, user2, disableUser)); - - db.organizations().assertUserIsMemberOfOrganization(organization, user1); - db.organizations().assertUserIsMemberOfOrganization(organization, user2); - assertUserIsNotMember(organization, disableUser); - assertThat(userIndex.search(UserQuery.builder().build(), new SearchOptions()).getDocs()) - .extracting(UserDoc::login, UserDoc::organizationUuids) - .containsExactlyInAnyOrder( - tuple(user1.getLogin(), singletonList(organization.getUuid())), - tuple(user2.getLogin(), singletonList(organization.getUuid()))); - } - - @Test - public void add_members_does_not_fail_when_one_user_is_already_member_of_organization() { - OrganizationDto organization = db.organizations().insert(); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - UserDto userAlreadyMember = db.users().insertUser(); - db.organizations().addMember(organization, userAlreadyMember); - db.users().insertMember(defaultGroup, userAlreadyMember); - UserDto userNotMember = db.users().insertUser(); - userIndexer.indexOnStartup(new HashSet<>()); - - underTest.addMembers(db.getSession(), organization, asList(userAlreadyMember, userNotMember)); - - db.organizations().assertUserIsMemberOfOrganization(organization, userAlreadyMember); - db.organizations().assertUserIsMemberOfOrganization(organization, userNotMember); - assertThat(userIndex.search(UserQuery.builder().build(), new SearchOptions()).getDocs()) - .extracting(UserDoc::login, UserDoc::organizationUuids) - .containsExactlyInAnyOrder( - tuple(userAlreadyMember.getLogin(), singletonList(organization.getUuid())), - tuple(userNotMember.getLogin(), singletonList(organization.getUuid()))); - } - - @Test - public void remove_member_from_db_and_user_index() { - OrganizationDto organization = db.organizations().insert(); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - UserDto user = db.users().insertUser(); - UserDto adminUser = db.users().insertAdminByUserPermission(organization); - db.organizations().addMember(organization, user, adminUser); - db.users().insertMember(defaultGroup, user); - userIndexer.indexOnStartup(new HashSet<>()); - - underTest.removeMember(db.getSession(), organization, user); - - assertUserIsNotMember(organization, user); - } - - @Test - public void remove_members_from_db_and_user_index() { - OrganizationDto organization = db.organizations().insert(); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - UserDto user1 = db.users().insertUser(); - UserDto user2 = db.users().insertUser(); - UserDto adminUser = db.users().insertAdminByUserPermission(organization); - db.organizations().addMember(organization, user1, user2, adminUser); - db.users().insertMember(defaultGroup, user1); - db.users().insertMember(defaultGroup, user2); - db.users().insertMember(defaultGroup, adminUser); - userIndexer.indexOnStartup(new HashSet<>()); - - underTest.removeMembers(db.getSession(), organization, asList(user1, user2)); - - assertUserIsNotMember(organization, user1); - assertUserIsNotMember(organization, user2); - db.organizations().assertUserIsMemberOfOrganization(organization, adminUser); - } - - @Test - public void remove_member_removes_permissions() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto project = db.components().insertPrivateProject(organization); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - UserDto user = db.users().insertUser(); - UserDto adminUser = db.users().insertAdminByUserPermission(organization); - db.organizations().addMember(organization, user, adminUser); - db.users().insertMember(defaultGroup, user); - UserDto anotherUser = db.users().insertUser(); - OrganizationDto anotherOrganization = db.organizations().insert(); - ComponentDto anotherProject = db.components().insertPrivateProject(anotherOrganization); - userIndexer.indexOnStartup(new HashSet<>()); - - db.users().insertPermissionOnUser(organization, user, ADMINISTER); - db.users().insertPermissionOnUser(organization, user, SCAN); - db.users().insertPermissionOnUser(anotherOrganization, user, ADMINISTER); - db.users().insertPermissionOnUser(anotherOrganization, user, SCAN); - db.users().insertPermissionOnUser(organization, anotherUser, ADMINISTER); - db.users().insertPermissionOnUser(organization, anotherUser, SCAN); - db.users().insertProjectPermissionOnUser(user, CODEVIEWER, project); - db.users().insertProjectPermissionOnUser(user, USER, project); - db.users().insertProjectPermissionOnUser(user, CODEVIEWER, anotherProject); - db.users().insertProjectPermissionOnUser(user, USER, anotherProject); - db.users().insertProjectPermissionOnUser(anotherUser, CODEVIEWER, project); - db.users().insertProjectPermissionOnUser(anotherUser, USER, project); - - underTest.removeMember(db.getSession(), organization, user); - - assertUserIsNotMember(organization, user); - assertOrgPermissionsOfUser(user, organization); - assertOrgPermissionsOfUser(user, anotherOrganization, ADMINISTER, SCAN); - assertOrgPermissionsOfUser(anotherUser, organization, ADMINISTER, SCAN); - assertProjectPermissionsOfUser(user, project); - assertProjectPermissionsOfUser(user, anotherProject, CODEVIEWER, USER); - assertProjectPermissionsOfUser(anotherUser, project, CODEVIEWER, USER); - } - - @Test - public void remove_member_removes_template_permissions() { - OrganizationDto organization = db.organizations().insert(); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - UserDto user = db.users().insertUser(); - UserDto adminUser = db.users().insertAdminByUserPermission(organization); - db.organizations().addMember(organization, user, adminUser); - db.users().insertMember(defaultGroup, user); - userIndexer.indexOnStartup(new HashSet<>()); - - OrganizationDto anotherOrganization = db.organizations().insert(); - UserDto anotherUser = db.users().insertUser(); - PermissionTemplateDto template = db.permissionTemplates().insertTemplate(organization); - PermissionTemplateDto anotherTemplate = db.permissionTemplates().insertTemplate(anotherOrganization); - String permission = "browse"; - db.permissionTemplates().addUserToTemplate(template.getId(), user.getId(), permission); - db.permissionTemplates().addUserToTemplate(template.getId(), anotherUser.getId(), permission); - db.permissionTemplates().addUserToTemplate(anotherTemplate.getId(), user.getId(), permission); - - underTest.removeMember(db.getSession(), organization, user); - - assertThat(dbClient.permissionTemplateDao().selectUserPermissionsByTemplateId(db.getSession(), template.getId())).extracting(PermissionTemplateUserDto::getUserId) - .containsOnly(anotherUser.getId()); - assertThat(dbClient.permissionTemplateDao().selectUserPermissionsByTemplateId(db.getSession(), anotherTemplate.getId())).extracting(PermissionTemplateUserDto::getUserId) - .containsOnly(user.getId()); - } - - @Test - public void remove_member_removes_qprofiles_user_permission() { - OrganizationDto organization = db.organizations().insert(); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - UserDto user = db.users().insertUser(); - UserDto adminUser = db.users().insertAdminByUserPermission(organization); - db.organizations().addMember(organization, user, adminUser); - db.users().insertMember(defaultGroup, user); - userIndexer.indexOnStartup(new HashSet<>()); - - OrganizationDto anotherOrganization = db.organizations().insert(); - db.organizations().addMember(anotherOrganization, user); - QProfileDto profile = db.qualityProfiles().insert(organization); - QProfileDto anotherProfile = db.qualityProfiles().insert(anotherOrganization); - db.qualityProfiles().addUserPermission(profile, user); - db.qualityProfiles().addUserPermission(anotherProfile, user); - - underTest.removeMember(db.getSession(), organization, user); - - assertThat(db.getDbClient().qProfileEditUsersDao().exists(db.getSession(), profile, user)).isFalse(); - assertThat(db.getDbClient().qProfileEditUsersDao().exists(db.getSession(), anotherProfile, user)).isTrue(); - } - - @Test - public void remove_member_removes_user_from_organization_groups() { - OrganizationDto organization = db.organizations().insert(); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - UserDto user = db.users().insertUser(); - UserDto adminUser = db.users().insertAdminByUserPermission(organization); - db.organizations().addMember(organization, user, adminUser); - db.users().insertMember(defaultGroup, user); - userIndexer.indexOnStartup(new HashSet<>()); - - OrganizationDto anotherOrganization = db.organizations().insert(); - UserDto anotherUser = db.users().insertUser(); - GroupDto group = db.users().insertGroup(organization); - GroupDto anotherGroup = db.users().insertGroup(anotherOrganization); - db.users().insertMembers(group, user, anotherUser); - db.users().insertMembers(anotherGroup, user, anotherUser); - - underTest.removeMember(db.getSession(), organization, user); - - assertThat(dbClient.groupMembershipDao().selectGroupIdsByUserId(db.getSession(), user.getId())) - .containsOnly(anotherGroup.getId()); - assertThat(dbClient.groupMembershipDao().selectGroupIdsByUserId(db.getSession(), anotherUser.getId())) - .containsOnly(group.getId(), anotherGroup.getId()); - } - - @Test - public void remove_member_removes_user_from_default_organization_group() { - OrganizationDto organization = db.organizations().insert(); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - UserDto user = db.users().insertUser(); - UserDto adminUser = db.users().insertAdminByUserPermission(organization); - db.organizations().addMember(organization, user, adminUser); - db.users().insertMember(defaultGroup, user); - userIndexer.indexOnStartup(new HashSet<>()); - - underTest.removeMember(db.getSession(), organization, user); - - assertThat(dbClient.groupMembershipDao().selectGroupIdsByUserId(db.getSession(), user.getId())).isEmpty(); - } - - @Test - public void remove_member_removes_user_from_org_properties() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto project = db.components().insertPrivateProject(organization); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - UserDto user = db.users().insertUser(); - UserDto adminUser = db.users().insertAdminByUserPermission(organization); - db.organizations().addMember(organization, user, adminUser); - db.users().insertMember(defaultGroup, user); - userIndexer.indexOnStartup(new HashSet<>()); - - OrganizationDto anotherOrganization = db.organizations().insert(); - ComponentDto anotherProject = db.components().insertPrivateProject(anotherOrganization); - UserDto anotherUser = db.users().insertUser(); - insertProperty("KEY_11", "VALUE", project.getId(), user.getId()); - insertProperty("KEY_12", "VALUE", project.getId(), user.getId()); - insertProperty("KEY_11", "VALUE", project.getId(), anotherUser.getId()); - insertProperty("KEY_11", "VALUE", anotherProject.getId(), user.getId()); - - underTest.removeMember(db.getSession(), organization, user); - - assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setComponentId(project.getId()).build(), db.getSession())) - .hasSize(1).extracting(PropertyDto::getUserId).containsOnly(anotherUser.getId()); - assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setComponentId(anotherProject.getId()).build(), db.getSession())).extracting(PropertyDto::getUserId) - .hasSize(1).containsOnly(user.getId()); - } - - @Test - public void remove_member_removes_user_from_default_assignee_properties() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto project = db.components().insertPrivateProject(organization); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - UserDto user = db.users().insertUser(); - UserDto adminUser = db.users().insertAdminByUserPermission(organization); - db.organizations().addMember(organization, user, adminUser); - db.users().insertMember(defaultGroup, user); - userIndexer.indexOnStartup(new HashSet<>()); - - OrganizationDto anotherOrganization = db.organizations().insert(); - ComponentDto anotherProject = db.components().insertPrivateProject(anotherOrganization); - UserDto anotherUser = db.users().insertUser(); - insertProperty(DEFAULT_ISSUE_ASSIGNEE, user.getLogin(), project.getId(), null); - insertProperty("ANOTHER_KEY", user.getLogin(), project.getId(), null); - insertProperty(DEFAULT_ISSUE_ASSIGNEE, anotherUser.getLogin(), project.getId(), null); - insertProperty(DEFAULT_ISSUE_ASSIGNEE, user.getLogin(), anotherProject.getId(), null); - - underTest.removeMember(db.getSession(), organization, user); - - assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setComponentId(project.getId()).build(), db.getSession())) - .hasSize(2).extracting(PropertyDto::getKey, PropertyDto::getValue) - .containsOnly(Tuple.tuple("ANOTHER_KEY", user.getLogin()), Tuple.tuple(DEFAULT_ISSUE_ASSIGNEE, anotherUser.getLogin())); - assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setComponentId(anotherProject.getId()).build(), db.getSession())).extracting(PropertyDto::getValue) - .hasSize(1).containsOnly(user.getLogin()); - } - - @Test - public void fail_to_remove_members_when_no_more_admin() { - OrganizationDto organization = db.organizations().insert(); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - GroupDto adminGroup = db.users().insertGroup(organization); - db.users().insertPermissionOnGroup(adminGroup, ADMINISTER); - UserDto user1 = db.users().insertUser(); - UserDto admin1 = db.users().insertAdminByUserPermission(organization); - UserDto admin2 = db.users().insertUser(); - db.organizations().addMember(organization, user1, admin1, admin2); - db.users().insertMember(defaultGroup, user1); - db.users().insertMember(defaultGroup, admin1); - db.users().insertMember(defaultGroup, admin2); - db.users().insertMember(adminGroup, admin2); - userIndexer.indexOnStartup(new HashSet<>()); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("The last administrator member cannot be removed"); - - underTest.removeMembers(db.getSession(), organization, asList(admin1, admin2)); - } - - @Test - public void synchronize_user_organization_membership() { - OrganizationDto organization1 = db.organizations().insert(); - GroupDto org1defaultGroup = db.users().insertDefaultGroup(organization1, "Members"); - AlmAppInstallDto gitHubInstall1 = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization1, gitHubInstall1, true); - OrganizationDto organization2 = db.organizations().insert(); - db.users().insertDefaultGroup(organization2, "Members"); - AlmAppInstallDto gitHubInstall2 = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization2, gitHubInstall2, true); - OrganizationDto organization3 = db.organizations().insert(); - GroupDto org3defaultGroup = db.users().insertDefaultGroup(organization3, "Members"); - AlmAppInstallDto gitHubInstall3 = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization3, gitHubInstall3, true); - // User is member of organization1 and organization3, but organization3 membership will be removed and organization2 membership will be - // added - UserDto user = db.users().insertUser(); - db.organizations().addMember(organization1, user); - db.users().insertMember(org1defaultGroup, user); - db.organizations().addMember(organization3, user); - db.users().insertMember(org3defaultGroup, user); - - underTest.synchronizeUserOrganizationMembership(db.getSession(), user, GITHUB, ImmutableSet.of(gitHubInstall1.getOrganizationAlmId(), gitHubInstall2.getOrganizationAlmId())); - - db.organizations().assertUserIsMemberOfOrganization(organization1, user); - db.organizations().assertUserIsMemberOfOrganization(organization2, user); - assertUserIsNotMember(organization3, user); - } - - @Test - public void synchronize_user_organization_membership_does_not_update_es_index() { - OrganizationDto organization = db.organizations().insert(); - db.users().insertDefaultGroup(organization, "Members"); - AlmAppInstallDto gitHubInstall = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization, gitHubInstall, true); - UserDto user = db.users().insertUser(); - - underTest.synchronizeUserOrganizationMembership(db.getSession(), user, GITHUB, ImmutableSet.of(gitHubInstall.getOrganizationAlmId())); - - assertThat(userIndex.search(UserQuery.builder().build(), new SearchOptions()).getDocs()).isEmpty(); - } - - @Test - public void synchronize_user_organization_membership_ignores_organization_alm_ids_match_no_existing_organizations() { - OrganizationDto organization = db.organizations().insert(); - db.users().insertDefaultGroup(organization, "Members"); - AlmAppInstallDto gitHubInstall = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization, gitHubInstall, true); - UserDto user = db.users().insertUser(); - - underTest.synchronizeUserOrganizationMembership(db.getSession(), user, GITHUB, ImmutableSet.of("unknown")); - - // User is member of no organization - assertThat(db.getDbClient().organizationMemberDao().selectOrganizationUuidsByUser(db.getSession(), user.getId())).isEmpty(); - } - - @Test - public void synchronize_user_organization_membership_ignores_organization_with_member_sync_disabled() { - OrganizationDto organization = db.organizations().insert(); - db.users().insertDefaultGroup(organization, "Members"); - AlmAppInstallDto gitHubInstall = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization, gitHubInstall, false); - UserDto user = db.users().insertUser(); - - underTest.synchronizeUserOrganizationMembership(db.getSession(), user, GITHUB, ImmutableSet.of(gitHubInstall.getOrganizationAlmId())); - - db.organizations().assertUserIsNotMemberOfOrganization(organization, user); - } - - @Test - public void synchronize_user_organization_membership_does_not_remove_existing_membership_on_organization_with_member_sync_disabled() { - OrganizationDto organization = db.organizations().insert(); - GroupDto org1defaultGroup = db.users().insertDefaultGroup(organization, "Members"); - AlmAppInstallDto gitHubInstall = db.alm().insertAlmAppInstall(a -> a.setAlm(GITHUB)); - db.alm().insertOrganizationAlmBinding(organization, gitHubInstall, false); - UserDto user = db.users().insertUser(); - db.users().insertMember(org1defaultGroup, user); - db.organizations().addMember(organization, user); - // User is member of a organization on which member sync is disabled - db.organizations().assertUserIsMemberOfOrganization(organization, user); - - // The organization is not in the list, but membership should not be removed - underTest.synchronizeUserOrganizationMembership(db.getSession(), user, GITHUB, ImmutableSet.of("other")); - - db.organizations().assertUserIsMemberOfOrganization(organization, user); - } - - private void assertUserIsNotMember(OrganizationDto organization, UserDto user) { - db.organizations().assertUserIsNotMemberOfOrganization(organization, user); - SearchRequestBuilder request = es.client().prepareSearch(UserIndexDefinition.TYPE_USER) - .setQuery(boolQuery() - .must(termQuery(FIELD_ORGANIZATION_UUIDS, organization.getUuid())) - .must(termQuery(FIELD_UUID, user.getUuid()))); - assertThat(request.get().getHits().getHits()).isEmpty(); - } - - private void assertOrgPermissionsOfUser(UserDto user, OrganizationDto organization, OrganizationPermission... permissions) { - assertThat(dbClient.userPermissionDao().selectGlobalPermissionsOfUser(db.getSession(), user.getId(), organization.getUuid()).stream() - .map(OrganizationPermission::fromKey)) - .containsOnly(permissions); - } - - private void assertProjectPermissionsOfUser(UserDto user, ComponentDto project, String... permissions) { - assertThat(dbClient.userPermissionDao().selectProjectPermissionsOfUser(db.getSession(), user.getId(), project.getId())).containsOnly(permissions); - } - - private void insertProperty(String key, @Nullable String value, @Nullable Long resourceId, @Nullable Integer userId) { - PropertyDto dto = new PropertyDto().setKey(key) - .setResourceId(resourceId) - .setUserId(userId) - .setValue(value); - db.properties().insertProperty(dto); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganisationSupportTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/OrganisationSupportTest.java deleted file mode 100644 index fc4e4ea60ee..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganisationSupportTest.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -import org.sonar.api.rule.RuleStatus; -import org.sonar.db.DbTester; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.permission.GroupPermissionDto; -import org.sonar.db.permission.template.PermissionTemplateDto; -import org.sonar.db.permission.template.PermissionTemplateGroupDto; -import org.sonar.db.rule.RuleDefinitionDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.es.EsTester; -import org.sonar.server.rule.index.RuleIndexer; -import org.sonar.server.usergroups.DefaultGroupCreatorImpl; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -public class OrganisationSupportTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Rule - public DbTester dbTester = DbTester.create(); - @Rule - public EsTester es = EsTester.create(); - - private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(dbTester); - private OrganizationFlags organizationFlags = new OrganizationFlagsImpl(dbTester.getDbClient()); - private RuleIndexer ruleIndexer = spy(new RuleIndexer(es.client(), dbTester.getDbClient())); - private OrganisationSupport underTest = new OrganisationSupport(dbTester.getDbClient(), defaultOrganizationProvider, organizationFlags, - new DefaultGroupCreatorImpl(dbTester.getDbClient()), new DefaultGroupFinder(dbTester.getDbClient()), ruleIndexer); - - @Test - public void enabling_support_saves_internal_property_and_flags_caller_as_root() { - UserDto user = dbTester.users().insertUser(); - UserDto otherUser = dbTester.users().insertUser(); - dbTester.users().insertDefaultGroup(dbTester.getDefaultOrganization(), "sonar-users"); - verifyFeatureEnabled(false); - verifyRoot(user, false); - verifyRoot(otherUser, false); - - call(user.getLogin()); - - verifyFeatureEnabled(true); - verifyRoot(user, true); - verifyRoot(otherUser, false); - } - - @Test - public void enabling_support_creates_default_members_group_and_associate_org_members() { - OrganizationDto defaultOrganization = dbTester.getDefaultOrganization(); - OrganizationDto anotherOrganization = dbTester.organizations().insert(); - UserDto user1 = dbTester.users().insertUser(); - UserDto user2 = dbTester.users().insertUser(); - UserDto userInAnotherOrganization = dbTester.users().insertUser(); - dbTester.organizations().addMember(defaultOrganization, user1); - dbTester.organizations().addMember(defaultOrganization, user2); - dbTester.organizations().addMember(anotherOrganization, userInAnotherOrganization); - dbTester.users().insertDefaultGroup(dbTester.getDefaultOrganization(), "sonar-users"); - - call(user1.getLogin()); - - Optional<Integer> defaultGroupId = dbTester.getDbClient().organizationDao().getDefaultGroupId(dbTester.getSession(), defaultOrganization.getUuid()); - assertThat(defaultGroupId).isPresent(); - GroupDto membersGroup = dbTester.getDbClient().groupDao().selectById(dbTester.getSession(), defaultGroupId.get()); - assertThat(membersGroup).isNotNull(); - assertThat(membersGroup.getName()).isEqualTo("Members"); - assertThat(dbTester.getDbClient().groupMembershipDao().selectGroupIdsByUserId(dbTester.getSession(), user1.getId())).containsOnly(defaultGroupId.get()); - assertThat(dbTester.getDbClient().groupMembershipDao().selectGroupIdsByUserId(dbTester.getSession(), user2.getId())).containsOnly(defaultGroupId.get()); - assertThat(dbTester.getDbClient().groupMembershipDao().selectGroupIdsByUserId(dbTester.getSession(), userInAnotherOrganization.getId())).isEmpty(); - } - - @Test - public void enabling_support_copy_sonar_users_permissions_to_members_group() { - OrganizationDto defaultOrganization = dbTester.getDefaultOrganization(); - UserDto user = dbTester.users().insertUser(); - GroupDto sonarUsersGroup = dbTester.users().insertDefaultGroup(defaultOrganization, "sonar-users"); - ComponentDto project = dbTester.components().insertPrivateProject(defaultOrganization); - dbTester.users().insertPermissionOnGroup(sonarUsersGroup, "user"); - dbTester.users().insertProjectPermissionOnGroup(sonarUsersGroup, "codeviewer", project); - // Should be ignored - GroupDto anotherGroup = dbTester.users().insertGroup(); - dbTester.users().insertPermissionOnGroup(anotherGroup, "admin"); - - call(user.getLogin()); - - int defaultGroupId = dbTester.getDbClient().organizationDao().getDefaultGroupId(dbTester.getSession(), defaultOrganization.getUuid()).get(); - assertThat(defaultGroupId).isNotEqualTo(sonarUsersGroup.getId()); - List<GroupPermissionDto> result = new ArrayList<>(); - dbTester.getDbClient().groupPermissionDao().selectAllPermissionsByGroupId(dbTester.getSession(), defaultOrganization.getUuid(), defaultGroupId, - context -> result.add((GroupPermissionDto) context.getResultObject())); - assertThat(result).extracting(GroupPermissionDto::getResourceId, GroupPermissionDto::getRole).containsOnly( - tuple(null, "user"), tuple(project.getId(), "codeviewer")); - } - - @Test - public void enabling_support_copy_sonar_users_permission_templates_to_members_group() { - OrganizationDto defaultOrganization = dbTester.getDefaultOrganization(); - UserDto user = dbTester.users().insertUser(); - GroupDto sonarUsersGroup = dbTester.users().insertDefaultGroup(defaultOrganization, "sonar-users"); - PermissionTemplateDto permissionTemplate = dbTester.permissionTemplates().insertTemplate(dbTester.getDefaultOrganization()); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, sonarUsersGroup, "user"); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, sonarUsersGroup, "admin"); - // Should be ignored - GroupDto otherGroup = dbTester.users().insertGroup(); - dbTester.permissionTemplates().addGroupToTemplate(permissionTemplate, otherGroup, "user"); - - call(user.getLogin()); - - int defaultGroupId = dbTester.getDbClient().organizationDao().getDefaultGroupId(dbTester.getSession(), defaultOrganization.getUuid()).get(); - assertThat(dbTester.getDbClient().permissionTemplateDao().selectAllGroupPermissionTemplatesByGroupId(dbTester.getSession(), defaultGroupId)) - .extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission) - .containsOnly(tuple(defaultGroupId, "user"), tuple(defaultGroupId, "admin")); - } - - @Test - public void enabling_organizations_should_remove_template_rule_and_custom_rule() { - RuleDefinitionDto normal = dbTester.rules().insert(); - RuleDefinitionDto template = dbTester.rules().insert(r -> r.setIsTemplate(true)); - RuleDefinitionDto custom = dbTester.rules().insert(r -> r.setTemplateId(template.getId())); - - UserDto user = dbTester.users().insertUser(); - dbTester.users().insertDefaultGroup(dbTester.getDefaultOrganization(), "sonar-users"); - - assertThat(dbTester.getDbClient().ruleDao().selectAllDefinitions(dbTester.getSession())) - .extracting(RuleDefinitionDto::getKey, RuleDefinitionDto::getStatus) - .containsExactlyInAnyOrder( - tuple(normal.getKey(), RuleStatus.READY), - tuple(template.getKey(), RuleStatus.READY), - tuple(custom.getKey(), RuleStatus.READY)); - - call(user.getLogin()); - - assertThat(dbTester.getDbClient().ruleDao().selectAllDefinitions(dbTester.getSession())) - .extracting(RuleDefinitionDto::getKey, RuleDefinitionDto::getStatus) - .containsExactlyInAnyOrder( - tuple(normal.getKey(), RuleStatus.READY), - tuple(template.getKey(), RuleStatus.REMOVED), - tuple(custom.getKey(), RuleStatus.REMOVED)); - - @SuppressWarnings("unchecked") - Class<ArrayList<Integer>> listClass = (Class<ArrayList<Integer>>) (Class) ArrayList.class; - ArgumentCaptor<ArrayList<Integer>> indexedRuleKeys = ArgumentCaptor.forClass(listClass); - verify(ruleIndexer).commitAndIndex(any(), indexedRuleKeys.capture()); - assertThat(indexedRuleKeys.getValue()).containsExactlyInAnyOrder(template.getId(), custom.getId()); - } - - @Test - public void throw_IAE_when_members_group_already_exists() { - UserDto user = dbTester.users().insertUser(); - dbTester.users().insertDefaultGroup(dbTester.getDefaultOrganization(), "sonar-users"); - dbTester.users().insertGroup(dbTester.getDefaultOrganization(), "Members"); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("The group 'Members' already exist"); - - call(user.getLogin()); - } - - @Test - public void do_nothing_if_support_is_already_enabled() { - dbTester.users().insertDefaultGroup(dbTester.getDefaultOrganization(), "sonar-users"); - - call("foo"); - verifyFeatureEnabled(true); - - // the test could be improved to verify that - // the caller user is not flagged as root - // if he was not already root - call("foo"); - verifyFeatureEnabled(true); - } - - private void call(String login) { - underTest.enable(login); - } - - private void verifyFeatureEnabled(boolean enabled) { - assertThat(organizationFlags.isEnabled(dbTester.getSession())).isEqualTo(enabled); - } - - private void verifyRoot(UserDto user, boolean root) { - dbTester.rootFlag().verify(user.getLogin(), root); - } - - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationUpdaterImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationUpdaterImplTest.java deleted file mode 100644 index 9eb1ed9f23b..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationUpdaterImplTest.java +++ /dev/null @@ -1,415 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; -import org.apache.commons.lang.RandomStringUtils; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.ResourceTypes; -import org.sonar.api.utils.System2; -import org.sonar.api.impl.utils.TestSystem2; -import org.sonar.api.web.UserRole; -import org.sonar.core.permission.GlobalPermissions; -import org.sonar.core.util.SequenceUuidFactory; -import org.sonar.core.util.UuidFactory; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.component.ResourceTypesRule; -import org.sonar.db.organization.DefaultTemplates; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.organization.OrganizationDto.Subscription; -import org.sonar.db.permission.template.PermissionTemplateDto; -import org.sonar.db.permission.template.PermissionTemplateGroupDto; -import org.sonar.db.qualitygate.QualityGateDto; -import org.sonar.db.qualityprofile.QProfileDto; -import org.sonar.db.qualityprofile.RulesProfileDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserMembershipDto; -import org.sonar.db.user.UserMembershipQuery; -import org.sonar.server.es.EsTester; -import org.sonar.server.es.SearchOptions; -import org.sonar.server.permission.PermissionService; -import org.sonar.server.permission.PermissionServiceImpl; -import org.sonar.server.qualityprofile.BuiltInQProfile; -import org.sonar.server.qualityprofile.BuiltInQProfileRepositoryRule; -import org.sonar.server.qualityprofile.QProfileName; -import org.sonar.server.user.index.UserIndex; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.user.index.UserQuery; -import org.sonar.server.usergroups.DefaultGroupCreator; -import org.sonar.server.usergroups.DefaultGroupCreatorImpl; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.assertj.core.api.Assertions.tuple; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.server.language.LanguageTesting.newLanguage; -import static org.sonar.server.organization.OrganizationUpdater.NewOrganization.newOrganizationBuilder; - -public class OrganizationUpdaterImplTest { - private static final long A_DATE = 12893434L; - - private OrganizationUpdater.NewOrganization FULL_POPULATED_NEW_ORGANIZATION = newOrganizationBuilder() - .setName("a-name") - .setKey("a-key") - .setDescription("a-description") - .setUrl("a-url") - .setAvatarUrl("a-avatar") - .build(); - - private System2 system2 = new TestSystem2().setNow(A_DATE); - - private static Consumer<OrganizationDto> EMPTY_ORGANIZATION_CONSUMER = o -> { - }; - - @Rule - public DbTester db = DbTester.create(system2); - @Rule - public EsTester es = EsTester.create(); - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Rule - public BuiltInQProfileRepositoryRule builtInQProfileRepositoryRule = new BuiltInQProfileRepositoryRule(); - - private DbSession dbSession = db.getSession(); - - private IllegalArgumentException exceptionThrownByOrganizationValidation = new IllegalArgumentException("simulate IAE thrown by OrganizationValidation"); - private DbClient dbClient = db.getDbClient(); - private UuidFactory uuidFactory = new SequenceUuidFactory(); - private OrganizationValidation organizationValidation = mock(OrganizationValidation.class); - private UserIndexer userIndexer = new UserIndexer(dbClient, es.client()); - private UserIndex userIndex = new UserIndex(es.client(), system2); - private DefaultGroupCreator defaultGroupCreator = new DefaultGroupCreatorImpl(dbClient); - - private ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT); - private PermissionService permissionService = new PermissionServiceImpl(resourceTypes); - - private OrganizationUpdaterImpl underTest = new OrganizationUpdaterImpl(dbClient, system2, uuidFactory, organizationValidation, userIndexer, - builtInQProfileRepositoryRule, defaultGroupCreator, permissionService); - - @Test - public void create_creates_organization_with_properties_from_NewOrganization_arg() throws OrganizationUpdater.KeyConflictException { - builtInQProfileRepositoryRule.initialize(); - UserDto user = db.users().insertUser(); - db.qualityGates().insertBuiltInQualityGate(); - - underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER); - - OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get(); - assertThat(organization.getUuid()).isNotEmpty(); - assertThat(organization.getKey()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getKey()); - assertThat(organization.getName()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getName()); - assertThat(organization.getDescription()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getDescription()); - assertThat(organization.getUrl()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getUrl()); - assertThat(organization.getAvatarUrl()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getAvatar()); - assertThat(organization.getSubscription()).isEqualTo(Subscription.FREE); - assertThat(organization.getCreatedAt()).isEqualTo(A_DATE); - assertThat(organization.getUpdatedAt()).isEqualTo(A_DATE); - } - - @Test - public void create_creates_owners_group_with_all_permissions_for_new_organization_and_add_current_user_to_it() throws OrganizationUpdater.KeyConflictException { - UserDto user = db.users().insertUser(); - builtInQProfileRepositoryRule.initialize(); - db.qualityGates().insertBuiltInQualityGate(); - - underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER); - - verifyGroupOwners(user, FULL_POPULATED_NEW_ORGANIZATION.getKey(), FULL_POPULATED_NEW_ORGANIZATION.getName()); - } - - @Test - public void create_creates_members_group_and_add_current_user_to_it() throws OrganizationUpdater.KeyConflictException { - UserDto user = db.users().insertUser(); - builtInQProfileRepositoryRule.initialize(); - db.qualityGates().insertBuiltInQualityGate(); - - underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER); - - verifyMembersGroup(user, FULL_POPULATED_NEW_ORGANIZATION.getKey()); - } - - @Test - public void create_does_not_require_description_url_and_avatar_to_be_non_null() throws OrganizationUpdater.KeyConflictException { - builtInQProfileRepositoryRule.initialize(); - UserDto user = db.users().insertUser(); - db.qualityGates().insertBuiltInQualityGate(); - - underTest.create(dbSession, user, newOrganizationBuilder() - .setKey("key") - .setName("name") - .build(), EMPTY_ORGANIZATION_CONSUMER); - - OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, "key").get(); - assertThat(organization.getKey()).isEqualTo("key"); - assertThat(organization.getName()).isEqualTo("name"); - assertThat(organization.getDescription()).isNull(); - assertThat(organization.getUrl()).isNull(); - assertThat(organization.getAvatarUrl()).isNull(); - } - - @Test - public void create_creates_default_template_for_new_organization() throws OrganizationUpdater.KeyConflictException { - builtInQProfileRepositoryRule.initialize(); - UserDto user = db.users().insertUser(); - db.qualityGates().insertBuiltInQualityGate(); - - underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER); - - OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get(); - GroupDto ownersGroup = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners").get(); - int defaultGroupId = dbClient.organizationDao().getDefaultGroupId(dbSession, organization.getUuid()).get(); - PermissionTemplateDto defaultTemplate = dbClient.permissionTemplateDao().selectByName(dbSession, organization.getUuid(), "default template"); - assertThat(defaultTemplate.getName()).isEqualTo("Default template"); - assertThat(defaultTemplate.getDescription()).isEqualTo("Default permission template of organization " + FULL_POPULATED_NEW_ORGANIZATION.getName()); - DefaultTemplates defaultTemplates = dbClient.organizationDao().getDefaultTemplates(dbSession, organization.getUuid()).get(); - assertThat(defaultTemplates.getProjectUuid()).isEqualTo(defaultTemplate.getUuid()); - assertThat(defaultTemplates.getApplicationsUuid()).isNull(); - assertThat(dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateId(dbSession, defaultTemplate.getId())) - .extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission) - .containsOnly( - tuple(ownersGroup.getId(), UserRole.ADMIN), - tuple(ownersGroup.getId(), GlobalPermissions.SCAN_EXECUTION), - tuple(defaultGroupId, UserRole.USER), - tuple(defaultGroupId, UserRole.CODEVIEWER), - tuple(defaultGroupId, UserRole.ISSUE_ADMIN), - tuple(defaultGroupId, UserRole.SECURITYHOTSPOT_ADMIN)); - } - - @Test - public void create_add_current_user_as_member_of_organization() throws OrganizationUpdater.KeyConflictException { - UserDto user = db.users().insertUser(); - builtInQProfileRepositoryRule.initialize(); - db.qualityGates().insertBuiltInQualityGate(); - - OrganizationDto result = underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER); - - assertThat(dbClient.organizationMemberDao().select(dbSession, result.getUuid(), user.getId())).isPresent(); - assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(result.getUuid()).setTextQuery(user.getLogin()).build(), new SearchOptions()).getTotal()).isEqualTo(1L); - } - - @Test - public void create_associates_to_built_in_quality_profiles() throws OrganizationUpdater.KeyConflictException { - BuiltInQProfile builtIn1 = builtInQProfileRepositoryRule.add(newLanguage("foo"), "qp1", true); - BuiltInQProfile builtIn2 = builtInQProfileRepositoryRule.add(newLanguage("foo"), "qp2"); - builtInQProfileRepositoryRule.initialize(); - insertRulesProfile(builtIn1); - insertRulesProfile(builtIn2); - UserDto user = db.users().insertUser(); - db.qualityGates().insertBuiltInQualityGate(); - - underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER); - - OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get(); - List<QProfileDto> profiles = dbClient.qualityProfileDao().selectOrderedByOrganizationUuid(dbSession, organization); - assertThat(profiles).extracting(p -> new QProfileName(p.getLanguage(), p.getName())).containsExactlyInAnyOrder( - builtIn1.getQProfileName(), builtIn2.getQProfileName()); - assertThat(dbClient.qualityProfileDao().selectDefaultProfile(dbSession, organization, "foo").getName()) - .isEqualTo("qp1"); - } - - private void insertRulesProfile(BuiltInQProfile builtIn) { - RulesProfileDto dto = new RulesProfileDto() - .setIsBuiltIn(true) - .setKee(RandomStringUtils.randomAlphabetic(40)) - .setLanguage(builtIn.getLanguage()) - .setName(builtIn.getName()); - dbClient.qualityProfileDao().insert(db.getSession(), dto); - db.commit(); - } - - @Test - public void create_associates_to_built_in_quality_gate() throws OrganizationUpdater.KeyConflictException { - QualityGateDto builtInQualityGate = db.qualityGates().insertBuiltInQualityGate(); - builtInQProfileRepositoryRule.initialize(); - UserDto user = db.users().insertUser(); - - underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, o -> { - }); - - OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get(); - assertThat(dbClient.qualityGateDao().selectDefault(dbSession, organization).getUuid()).isEqualTo(builtInQualityGate.getUuid()); - } - - @Test - public void create_calls_consumer() throws OrganizationUpdater.KeyConflictException { - UserDto user = db.users().insertUser(); - builtInQProfileRepositoryRule.initialize(); - db.qualityGates().insertBuiltInQualityGate(); - Boolean[] isConsumerCalled = new Boolean[]{false}; - - underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, o -> { - isConsumerCalled[0] = true; - }); - - assertThat(isConsumerCalled[0]).isEqualTo(true); - } - - @Test - public void create_throws_NPE_if_NewOrganization_arg_is_null() throws OrganizationUpdater.KeyConflictException { - UserDto user = db.users().insertUser(); - - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("newOrganization can't be null"); - - underTest.create(dbSession, user, null, EMPTY_ORGANIZATION_CONSUMER); - } - - @Test - public void create_throws_exception_thrown_by_checkValidKey() throws OrganizationUpdater.KeyConflictException { - UserDto user = db.users().insertUser(); - - when(organizationValidation.checkKey(FULL_POPULATED_NEW_ORGANIZATION.getKey())) - .thenThrow(exceptionThrownByOrganizationValidation); - - createThrowsExceptionThrownByOrganizationValidation(user); - } - - @Test - public void create_throws_exception_thrown_by_checkValidDescription() throws OrganizationUpdater.KeyConflictException { - UserDto user = db.users().insertUser(); - - when(organizationValidation.checkDescription(FULL_POPULATED_NEW_ORGANIZATION.getDescription())).thenThrow(exceptionThrownByOrganizationValidation); - - createThrowsExceptionThrownByOrganizationValidation(user); - } - - @Test - public void create_throws_exception_thrown_by_checkValidUrl() throws OrganizationUpdater.KeyConflictException { - UserDto user = db.users().insertUser(); - - when(organizationValidation.checkUrl(FULL_POPULATED_NEW_ORGANIZATION.getUrl())).thenThrow(exceptionThrownByOrganizationValidation); - - createThrowsExceptionThrownByOrganizationValidation(user); - } - - @Test - public void create_throws_exception_thrown_by_checkValidAvatar() throws OrganizationUpdater.KeyConflictException { - UserDto user = db.users().insertUser(); - - when(organizationValidation.checkAvatar(FULL_POPULATED_NEW_ORGANIZATION.getAvatar())).thenThrow(exceptionThrownByOrganizationValidation); - - createThrowsExceptionThrownByOrganizationValidation(user); - } - - private void createThrowsExceptionThrownByOrganizationValidation(UserDto user) throws OrganizationUpdater.KeyConflictException { - try { - underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER); - fail(exceptionThrownByOrganizationValidation + " should have been thrown"); - } catch (IllegalArgumentException e) { - assertThat(e).isSameAs(exceptionThrownByOrganizationValidation); - } - } - - @Test - public void create_fails_with_KeyConflictException_if_org_with_key_in_NewOrganization_arg_already_exists_in_db() throws OrganizationUpdater.KeyConflictException { - db.organizations().insertForKey(FULL_POPULATED_NEW_ORGANIZATION.getKey()); - UserDto user = db.users().insertUser(); - - expectedException.expect(OrganizationUpdater.KeyConflictException.class); - expectedException.expectMessage("Organization key '" + FULL_POPULATED_NEW_ORGANIZATION.getKey() + "' is already used"); - - underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER); - } - - @Test - public void update_personal_organization() { - OrganizationDto organization = db.organizations().insert(o -> o.setKey("old login")); - when(organizationValidation.generateKeyFrom("new_login")).thenReturn("new_login"); - - underTest.updateOrganizationKey(dbSession, organization, "new_login"); - - OrganizationDto organizationReloaded = dbClient.organizationDao().selectByUuid(dbSession, organization.getUuid()).get(); - assertThat(organizationReloaded.getKey()).isEqualTo("new_login"); - } - - @Test - public void does_not_update_personal_organization_when_generated_organization_key_does_not_change() { - OrganizationDto organization = db.organizations().insert(o -> o.setKey("login")); - when(organizationValidation.generateKeyFrom("Login")).thenReturn("login"); - - underTest.updateOrganizationKey(dbSession, organization, "Login"); - - OrganizationDto organizationReloaded = dbClient.organizationDao().selectByUuid(dbSession, organization.getUuid()).get(); - assertThat(organizationReloaded.getKey()).isEqualTo("login"); - } - - @Test - public void fail_to_update_personal_organization_when_new_key_already_exist() { - OrganizationDto organization = db.organizations().insert(); - db.organizations().insert(o -> o.setKey("new_login")); - when(organizationValidation.generateKeyFrom("new_login")).thenReturn("new_login"); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Can't create organization with key 'new_login' because an organization with this key already exists"); - - underTest.updateOrganizationKey(dbSession, organization, "new_login"); - } - - private void verifyGroupOwners(UserDto user, String organizationKey, String organizationName) { - OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey).get(); - Optional<GroupDto> groupOpt = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners"); - assertThat(groupOpt).isPresent(); - GroupDto groupDto = groupOpt.get(); - assertThat(groupDto.getDescription()).isEqualTo("Owners of organization"); - - assertThat(dbClient.groupPermissionDao().selectGlobalPermissionsOfGroup(dbSession, groupDto.getOrganizationUuid(), groupDto.getId())) - .containsOnly(GlobalPermissions.ALL.toArray(new String[GlobalPermissions.ALL.size()])); - List<UserMembershipDto> members = dbClient.groupMembershipDao().selectMembers( - dbSession, - UserMembershipQuery.builder() - .organizationUuid(organization.getUuid()) - .groupId(groupDto.getId()) - .membership(UserMembershipQuery.IN).build(), - 0, Integer.MAX_VALUE); - assertThat(members) - .extracting(UserMembershipDto::getLogin) - .containsOnly(user.getLogin()); - } - - private void verifyMembersGroup(UserDto user, String organizationKey) { - OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey).get(); - Optional<GroupDto> groupOpt = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Members"); - assertThat(groupOpt).isPresent(); - GroupDto groupDto = groupOpt.get(); - assertThat(groupDto.getDescription()).isEqualTo("All members of the organization"); - - assertThat(dbClient.groupPermissionDao().selectGlobalPermissionsOfGroup(dbSession, groupDto.getOrganizationUuid(), groupDto.getId())).isEmpty(); - List<UserMembershipDto> members = dbClient.groupMembershipDao().selectMembers( - dbSession, - UserMembershipQuery.builder() - .organizationUuid(organization.getUuid()) - .groupId(groupDto.getId()) - .membership(UserMembershipQuery.IN).build(), - 0, Integer.MAX_VALUE); - assertThat(members) - .extracting(UserMembershipDto::getLogin) - .containsOnly(user.getLogin()); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationValidationImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationValidationImplTest.java deleted file mode 100644 index 1f9cfe7ce1f..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationValidationImplTest.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.organization; - -import com.google.common.base.Strings; -import java.util.Random; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; - -public class OrganizationValidationImplTest { - private static final String STRING_32_CHARS = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - private static final String STRING_64_CHARS = STRING_32_CHARS + STRING_32_CHARS; - private static final String STRING_256_CHARS = STRING_64_CHARS + STRING_64_CHARS + STRING_64_CHARS + STRING_64_CHARS; - - private static final String STRING_255_CHARS = Strings.repeat("a", 255); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private OrganizationValidationImpl underTest = new OrganizationValidationImpl(); - - @Test - public void checkValidKey_throws_NPE_if_arg_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("key can't be null"); - - underTest.checkKey(null); - } - - @Test - public void checkValidKey_throws_IAE_if_arg_is_empty() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Key must not be empty"); - - underTest.checkKey(""); - } - - @Test - public void checkValidKey_throws_IAE_if_key_is_empty() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Key must not be empty"); - - underTest.checkKey(""); - } - - @Test - public void checkValidKey_does_not_fail_if_arg_is_1_to_255_chars_long() { - String str = "a"; - for (int i = 0; i < 254; i++) { - underTest.checkKey(str); - str += "a"; - } - } - - @Test - public void checkValidKey_throws_IAE_when_more_than_300_characters() { - String key = STRING_255_CHARS + "b"; - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Key '" + key + "' must be at most 255 chars long"); - - underTest.checkKey(key); - } - - @Test - public void checkValidKey_throws_IAE_if_arg_contains_invalid_chars() { - char[] invalidChars = {'é', '<', '@'}; - - for (char invalidChar : invalidChars) { - String str = "aa" + invalidChar; - try { - underTest.checkKey(str); - fail("A IllegalArgumentException should have been thrown"); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("Key '" + str + "' contains at least one invalid char"); - } - } - } - - @Test - public void checkValidName_throws_NPE_if_arg_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("name can't be null"); - - underTest.checkName(null); - } - - @Test - public void checkValidName_throws_IAE_if_empty() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Name must not be empty"); - - underTest.checkName(""); - } - - @Test - public void checkValidName_does_not_fail_if_arg_is_1_to_255_chars_long() { - String str = "a"; - for (int i = 0; i < 254; i++) { - underTest.checkName(str); - str += "a"; - } - } - - @Test - public void checkValidName_throws_IAE_when_more_than_255_characters() { - String str = STRING_255_CHARS + "b"; - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Name '" + str + "' must be at most 255 chars long"); - - underTest.checkName(str); - } - - @Test - public void checkValidDescription_does_not_fail_if_arg_is_null() { - underTest.checkDescription(null); - } - - @Test - public void checkValidDescription_does_not_fail_if_arg_is_empty() { - underTest.checkDescription(""); - } - - @Test - public void checkValidDescription_does_not_fail_if_arg_is_1_to_256_chars_long() { - String str = "1"; - for (int i = 0; i < 256; i++) { - underTest.checkDescription(str); - str += "a"; - } - } - - @Test - public void checkValidDescription_throws_IAE_if_arg_is_more_than_256_chars_long() { - String str = STRING_256_CHARS; - underTest.checkDescription(str); - for (int i = 0; i < 5 + Math.abs(new Random().nextInt(10)); i++) { - str += "c"; - try { - underTest.checkDescription(str); - fail("A IllegalArgumentException should have been thrown"); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("Description '" + str + "' must be at most 256 chars long"); - } - } - } - - @Test - public void checkValidUrl_does_not_fail_if_arg_is_null() { - underTest.checkUrl(null); - } - - @Test - public void checkValidUrl_does_not_fail_if_arg_is_1_to_256_chars_long() { - String str = "1"; - for (int i = 0; i < 256; i++) { - underTest.checkUrl(str); - str += "a"; - } - } - - @Test - public void checkValidUrl_throws_IAE_if_arg_is_more_than_256_chars_long() { - String str = STRING_256_CHARS; - underTest.checkUrl(str); - for (int i = 0; i < 5 + Math.abs(new Random().nextInt(10)); i++) { - str += "c"; - try { - underTest.checkUrl(str); - fail("A IllegalArgumentException should have been thrown"); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("Url '" + str + "' must be at most 256 chars long"); - } - } - } - - @Test - public void checkValidAvatar_does_not_fail_if_arg_is_null() { - underTest.checkAvatar(null); - } - - @Test - public void checkValidAvatar_does_not_fail_if_arg_is_1_to_256_chars_long() { - String str = "1"; - for (int i = 0; i < 256; i++) { - underTest.checkAvatar(str); - str += "a"; - } - } - - @Test - public void checkValidAvatar_throws_IAE_if_arg_is_more_than_256_chars_long() { - String str = STRING_256_CHARS; - underTest.checkAvatar(str); - for (int i = 0; i < 5 + Math.abs(new Random().nextInt(10)); i++) { - str += "c"; - try { - underTest.checkAvatar(str); - fail("A IllegalArgumentException should have been thrown"); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("Avatar '" + str + "' must be at most 256 chars long"); - } - } - } - - @Test - public void generateKeyFrom_returns_slug_of_arg() { - assertThat(underTest.generateKeyFrom("foo")).isEqualTo("foo"); - assertThat(underTest.generateKeyFrom(" FOO ")).isEqualTo("foo"); - assertThat(underTest.generateKeyFrom("he's here")).isEqualTo("he-s-here"); - assertThat(underTest.generateKeyFrom("foo-bar")).isEqualTo("foo-bar"); - assertThat(underTest.generateKeyFrom("foo_bar")).isEqualTo("foo_bar"); - assertThat(underTest.generateKeyFrom("accents éà")).isEqualTo("accents-ea"); - assertThat(underTest.generateKeyFrom("<foo>")).isEqualTo("foo"); - assertThat(underTest.generateKeyFrom("<\"foo:\">")).isEqualTo("foo"); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/PermissionServiceImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/PermissionServiceImplTest.java deleted file mode 100644 index e6abb50487f..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/permission/PermissionServiceImplTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.permission; - -import org.junit.Test; -import org.sonar.db.component.ResourceTypesRule; -import org.sonar.db.permission.OrganizationPermission; - -import static org.assertj.core.api.Assertions.assertThat; - -public class PermissionServiceImplTest { - - private ResourceTypesRule resourceTypesRule = new ResourceTypesRule().setRootQualifiers("APP", "VW"); - private PermissionServiceImpl underTest = new PermissionServiceImpl(resourceTypesRule); - - @Test - public void organizationPermissions_must_be_ordered() { - assertThat(underTest.getAllOrganizationPermissions()) - .extracting(OrganizationPermission::getKey) - .containsExactly("admin", "gateadmin", "profileadmin", "provisioning", "scan", "applicationcreator", "portfoliocreator"); - } - - @Test - public void projectPermissions_must_be_ordered() { - assertThat(underTest.getAllProjectPermissions()) - .containsExactly("admin", "codeviewer", "issueadmin", "securityhotspotadmin", "scan", "user"); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationHandlerTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationHandlerTest.java deleted file mode 100644 index 361f291edb4..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationHandlerTest.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.Random; -import java.util.Set; -import java.util.stream.IntStream; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.EmailSubscriberDto; -import org.sonar.db.permission.AuthorizationDao; -import org.sonar.server.notification.email.EmailNotificationChannel; - -import static java.util.Collections.emptySet; -import static java.util.stream.Collectors.toSet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -public class BuiltInQPChangeNotificationHandlerTest { - private DbClient dbClient = mock(DbClient.class); - private DbSession dbSession = mock(DbSession.class); - private AuthorizationDao authorizationDao = mock(AuthorizationDao.class); - private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class); - - private BuiltInQPChangeNotificationHandler underTest = new BuiltInQPChangeNotificationHandler(dbClient, emailNotificationChannel); - - @Before - public void wire_mocks() { - when(dbClient.openSession(false)).thenReturn(dbSession); - when(dbClient.authorizationDao()).thenReturn(authorizationDao); - } - - @Test - public void getMetadata_returns_empty() { - assertThat(underTest.getMetadata()).isEmpty(); - } - - @Test - public void getNotificationClass_is_BuiltInQPChangeNotification() { - assertThat(underTest.getNotificationClass()).isEqualTo(BuiltInQPChangeNotification.class); - } - - @Test - public void deliver_has_no_effect_if_emailNotificationChannel_is_disabled() { - when(emailNotificationChannel.isActivated()).thenReturn(false); - Set<BuiltInQPChangeNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10)) - .mapToObj(i -> mock(BuiltInQPChangeNotification.class)) - .collect(toSet()); - - int deliver = underTest.deliver(notifications); - - assertThat(deliver).isZero(); - verify(emailNotificationChannel).isActivated(); - verifyNoMoreInteractions(emailNotificationChannel); - verifyZeroInteractions(dbClient); - notifications.forEach(Mockito::verifyZeroInteractions); - } - - @Test - public void deliver_has_no_effect_if_there_is_no_global_administer_email_subscriber() { - when(emailNotificationChannel.isActivated()).thenReturn(true); - Set<BuiltInQPChangeNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10)) - .mapToObj(i -> mock(BuiltInQPChangeNotification.class)) - .collect(toSet()); - when(authorizationDao.selectQualityProfileAdministratorLogins(dbSession)) - .thenReturn(emptySet()); - - int deliver = underTest.deliver(notifications); - - assertThat(deliver).isZero(); - verify(emailNotificationChannel).isActivated(); - verifyNoMoreInteractions(emailNotificationChannel); - verify(dbClient).openSession(false); - verify(dbClient).authorizationDao(); - verifyNoMoreInteractions(dbClient); - verify(authorizationDao).selectQualityProfileAdministratorLogins(dbSession); - verifyNoMoreInteractions(authorizationDao); - notifications.forEach(Mockito::verifyZeroInteractions); - } - - @Test - public void deliver_create_emailRequest_for_each_notification_and_for_each_global_administer_email_subscriber() { - when(emailNotificationChannel.isActivated()).thenReturn(true); - Set<BuiltInQPChangeNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10)) - .mapToObj(i -> mock(BuiltInQPChangeNotification.class)) - .collect(toSet()); - Set<EmailSubscriberDto> emailSubscribers = IntStream.range(0, 1 + new Random().nextInt(10)) - .mapToObj(i -> EmailSubscriberDto.create("login_" + i, true, "login_" + i + "@foo")) - .collect(toSet()); - when(authorizationDao.selectQualityProfileAdministratorLogins(dbSession)) - .thenReturn(emailSubscribers); - Set<EmailNotificationChannel.EmailDeliveryRequest> expectedRequests = notifications.stream() - .flatMap(notification -> emailSubscribers.stream().map(subscriber -> new EmailNotificationChannel.EmailDeliveryRequest(subscriber.getEmail(), notification))) - .collect(toSet()); - int deliveries = new Random().nextInt(expectedRequests.size()); - when(emailNotificationChannel.deliverAll(expectedRequests)).thenReturn(deliveries); - - int deliver = underTest.deliver(notifications); - - assertThat(deliver).isEqualTo(deliveries); - verify(emailNotificationChannel).isActivated(); - verify(emailNotificationChannel).deliverAll(expectedRequests); - verifyNoMoreInteractions(emailNotificationChannel); - verify(dbClient).openSession(false); - verify(dbClient).authorizationDao(); - verifyNoMoreInteractions(dbClient); - verify(authorizationDao).selectQualityProfileAdministratorLogins(dbSession); - verifyNoMoreInteractions(authorizationDao); - notifications.forEach(Mockito::verifyZeroInteractions); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationTemplateTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationTemplateTest.java deleted file mode 100644 index 3324486882b..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationTemplateTest.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.Date; -import org.junit.Before; -import org.junit.Test; -import org.sonar.api.platform.Server; -import org.sonar.server.issue.notification.EmailMessage; -import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.Profile; - -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.api.utils.DateUtils.formatDate; - -public class BuiltInQPChangeNotificationTemplateTest { - - private Server server = mock(Server.class); - - private BuiltInQPChangeNotificationTemplate underTest = new BuiltInQPChangeNotificationTemplate(server); - - @Before - public void setUp() throws Exception { - when(server.getPublicRootUrl()).thenReturn("http://" + randomAlphanumeric(10)); - } - - @Test - public void notification_contains_a_subject() { - String profileName = newProfileName(); - String languageKey = newLanguageKey(); - String languageName = newLanguageName(); - BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName(profileName) - .setLanguageKey(languageKey) - .setLanguageName(languageName) - .setNewRules(2) - .build()); - - EmailMessage emailMessage = underTest.format(notification.build()); - - assertThat(emailMessage.getSubject()).isEqualTo("Built-in quality profiles have been updated"); - } - - @Test - public void notification_contains_count_of_new_rules() { - String profileName = newProfileName(); - String languageKey = newLanguageKey(); - String languageName = newLanguageName(); - BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName(profileName) - .setLanguageKey(languageKey) - .setLanguageName(languageName) - .setNewRules(2) - .build()); - - EmailMessage emailMessage = underTest.format(notification.build()); - - assertMessage(emailMessage, "\n 2 new rules\n"); - } - - @Test - public void notification_contains_count_of_updated_rules() { - String profileName = newProfileName(); - String languageKey = newLanguageKey(); - String languageName = newLanguageName(); - BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName(profileName) - .setLanguageKey(languageKey) - .setLanguageName(languageName) - .setUpdatedRules(2) - .build()); - - EmailMessage emailMessage = underTest.format(notification.build()); - - assertMessage(emailMessage, "\n 2 rules have been updated\n"); - } - - @Test - public void notification_contains_count_of_removed_rules() { - String profileName = newProfileName(); - String languageKey = newLanguageKey(); - String languageName = newLanguageName(); - BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName(profileName) - .setLanguageKey(languageKey) - .setLanguageName(languageName) - .setRemovedRules(2) - .build()); - - EmailMessage emailMessage = underTest.format(notification.build()); - - assertMessage(emailMessage, "\n 2 rules removed\n"); - } - - @Test - public void notification_supports_grammar_for_single_rule_added_removed_or_updated() { - String profileName = newProfileName(); - String languageKey = newLanguageKey(); - String languageName = newLanguageName(); - BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName(profileName) - .setLanguageKey(languageKey) - .setLanguageName(languageName) - .setNewRules(1) - .setUpdatedRules(1) - .setRemovedRules(1) - .build()); - - EmailMessage emailMessage = underTest.format(notification.build()); - - assertThat(emailMessage.getMessage()) - .contains("\n 1 new rule\n") - .contains("\n 1 rule has been updated\n") - .contains("\n 1 rule removed\n"); - } - - @Test - public void notification_contains_list_of_new_updated_and_removed_rules() { - String profileName = newProfileName(); - String languageKey = newLanguageKey(); - String languageName = newLanguageName(); - BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName(profileName) - .setLanguageKey(languageKey) - .setLanguageName(languageName) - .setNewRules(2) - .setUpdatedRules(3) - .setRemovedRules(4) - .build()); - - EmailMessage emailMessage = underTest.format(notification.build()); - - assertMessage(emailMessage, - "\n" + - " 2 new rules\n" + - " 3 rules have been updated\n" + - " 4 rules removed\n"); - } - - @Test - public void notification_contains_many_profiles() { - String profileName1 = "profile1_" + randomAlphanumeric(20); - String languageKey1 = "langkey1_" + randomAlphanumeric(20); - String languageName1 = "langName1_" + randomAlphanumeric(20); - String profileName2 = "profile2_" + randomAlphanumeric(20); - String languageKey2 = "langkey2_" + randomAlphanumeric(20); - String languageName2 = "langName2_" + randomAlphanumeric(20); - BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName(profileName1) - .setLanguageKey(languageKey1) - .setLanguageName(languageName1) - .setNewRules(2) - .build()) - .addProfile(Profile.newBuilder() - .setProfileName(profileName2) - .setLanguageKey(languageKey2) - .setLanguageName(languageName2) - .setNewRules(13) - .build()); - - EmailMessage emailMessage = underTest.format(notification.build()); - - assertThat(emailMessage.getMessage()).containsSubsequence("The following built-in profiles have been updated:\n", - profileTitleText(profileName1, languageKey1, languageName1), - " 2 new rules\n", - profileTitleText(profileName2, languageKey2, languageName2), - " 13 new rules\n", - "This is a good time to review your quality profiles and update them to benefit from the latest evolutions: " + server.getPublicRootUrl() + "/profiles"); - } - - @Test - public void notification_contains_profiles_sorted_by_language_then_by_profile_name() { - String languageKey1 = "langkey1_" + randomAlphanumeric(20); - String languageName1 = "langName1_" + randomAlphanumeric(20); - String languageKey2 = "langKey2_" + randomAlphanumeric(20); - String languageName2 = "langName2_" + randomAlphanumeric(20); - String profileName1 = "profile1_" + randomAlphanumeric(20); - String profileName2 = "profile2_" + randomAlphanumeric(20); - String profileName3 = "profile3_" + randomAlphanumeric(20); - BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder().setProfileName(profileName3).setLanguageKey(languageKey2).setLanguageName(languageName2).build()) - .addProfile(Profile.newBuilder().setProfileName(profileName2).setLanguageKey(languageKey1).setLanguageName(languageName1).build()) - .addProfile(Profile.newBuilder().setProfileName(profileName1).setLanguageKey(languageKey2).setLanguageName(languageName2).build()); - - EmailMessage emailMessage = underTest.format(notification.build()); - - assertThat(emailMessage.getMessage()).containsSubsequence( - "\"" + profileName2 + "\" - " + languageName1, - "\"" + profileName1 + "\" - " + languageName2, - "\"" + profileName3 + "\" - " + languageName2); - } - - @Test - public void notification_contains_encoded_profile_name() { - BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName("Sonar Way") - .setLanguageKey("java") - .setLanguageName(newLanguageName()) - .build()); - - EmailMessage emailMessage = underTest.format(notification.build()); - - assertThat(emailMessage.getMessage()).contains(server.getPublicRootUrl() + "/profiles/changelog?language=java&name=Sonar+Way"); - } - - @Test - public void notification_contains_from_and_to_date() { - String profileName = newProfileName(); - String languageKey = newLanguageKey(); - String languageName = newLanguageName(); - long startDate = 1_000_000_000_000L; - long endDate = startDate + 1_100_000_000_000L; - BuiltInQPChangeNotificationBuilder notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName(profileName) - .setLanguageKey(languageKey) - .setLanguageName(languageName) - .setStartDate(startDate) - .setEndDate(endDate) - .build()); - - EmailMessage emailMessage = underTest.format(notification.build()); - - assertMessage(emailMessage, - profileTitleText(profileName, languageKey, languageName, formatDate(new Date(startDate)), formatDate(new Date(endDate)))); - } - - private void assertMessage(EmailMessage emailMessage, String expectedProfileDetails) { - assertThat(emailMessage.getMessage()) - .containsSubsequence( - "The following built-in profiles have been updated:\n\n", - expectedProfileDetails, - "\nThis is a good time to review your quality profiles and update them to benefit from the latest evolutions: " + server.getPublicRootUrl() + "/profiles"); - } - - private String profileTitleText(String profileName, String languageKey, String languageName) { - return "\"" + profileName + "\" - " + languageName + ": " + server.getPublicRootUrl() + "/profiles/changelog?language=" + languageKey + "&name=" + profileName; - } - - private String profileTitleText(String profileName, String languageKey, String languageName, String startDate, String endDate) { - return "\"" + profileName + "\" - " + languageName + ": " + server.getPublicRootUrl() + "/profiles/changelog?language=" + languageKey + "&name=" + profileName + - "&since=" + startDate + "&to=" + endDate + "\n"; - } - - private static String newProfileName() { - return "profileName_" + randomAlphanumeric(20); - } - - private static String newLanguageName() { - return "languageName_" + randomAlphanumeric(20); - } - - private static String newLanguageKey() { - return "languageKey_" + randomAlphanumeric(20); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationTest.java deleted file mode 100644 index d3cd519bb2a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.Random; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.notifications.Notification; -import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.Profile; - -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; - -public class BuiltInQPChangeNotificationTest { - - private static final Random RANDOM = new Random(); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Test - public void serialize_and_parse_no_profile() { - Notification notification = new BuiltInQPChangeNotificationBuilder().build(); - - BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification); - - assertThat(result.getProfiles()).isEmpty(); - } - - @Test - public void serialize_and_parse_single_profile() { - String profileName = randomAlphanumeric(20); - String languageKey = randomAlphanumeric(20); - String languageName = randomAlphanumeric(20); - int newRules = RANDOM.nextInt(5000); - int updatedRules = RANDOM.nextInt(5000); - int removedRules = RANDOM.nextInt(5000); - long startDate = RANDOM.nextInt(5000); - long endDate = startDate + RANDOM.nextInt(5000); - - BuiltInQPChangeNotification notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName(profileName) - .setLanguageKey(languageKey) - .setLanguageName(languageName) - .setNewRules(newRules) - .setUpdatedRules(updatedRules) - .setRemovedRules(removedRules) - .setStartDate(startDate) - .setEndDate(endDate) - .build()) - .build(); - BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification); - - assertThat(result.getProfiles()) - .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules, Profile::getUpdatedRules, Profile::getRemovedRules, - Profile::getStartDate, Profile::getEndDate) - .containsExactlyInAnyOrder(tuple(profileName, languageKey, languageName, newRules, updatedRules, removedRules, startDate, endDate)); - } - - @Test - public void serialize_and_parse_multiple_profiles() { - String profileName1 = randomAlphanumeric(20); - String languageKey1 = randomAlphanumeric(20); - String languageName1 = randomAlphanumeric(20); - String profileName2 = randomAlphanumeric(20); - String languageKey2 = randomAlphanumeric(20); - String languageName2 = randomAlphanumeric(20); - - BuiltInQPChangeNotification notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName(profileName1) - .setLanguageKey(languageKey1) - .setLanguageName(languageName1) - .build()) - .addProfile(Profile.newBuilder() - .setProfileName(profileName2) - .setLanguageKey(languageKey2) - .setLanguageName(languageName2) - .build()) - .build(); - BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification); - - assertThat(result.getProfiles()).extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName) - .containsExactlyInAnyOrder(tuple(profileName1, languageKey1, languageName1), tuple(profileName2, languageKey2, languageName2)); - } - - @Test - public void serialize_and_parse_max_values() { - String profileName = randomAlphanumeric(20); - String languageKey = randomAlphanumeric(20); - String languageName = randomAlphanumeric(20); - int newRules = Integer.MAX_VALUE; - int updatedRules = Integer.MAX_VALUE; - int removedRules = Integer.MAX_VALUE; - long startDate = Long.MAX_VALUE; - long endDate = Long.MAX_VALUE; - - BuiltInQPChangeNotification notification = new BuiltInQPChangeNotificationBuilder() - .addProfile(Profile.newBuilder() - .setProfileName(profileName) - .setLanguageKey(languageKey) - .setLanguageName(languageName) - .setNewRules(newRules) - .setUpdatedRules(updatedRules) - .setRemovedRules(removedRules) - .setStartDate(startDate) - .setEndDate(endDate) - .build()) - .build(); - BuiltInQPChangeNotificationBuilder result = BuiltInQPChangeNotificationBuilder.parse(notification); - - assertThat(result.getProfiles()) - .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules, Profile::getUpdatedRules, Profile::getRemovedRules, - Profile::getStartDate, Profile::getEndDate) - .containsExactlyInAnyOrder(tuple(profileName, languageKey, languageName, newRules, updatedRules, removedRules, startDate, endDate)); - } - - @Test - public void fail_with_ISE_when_parsing_empty_notification() { - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Could not read the built-in quality profile notification"); - - BuiltInQPChangeNotificationBuilder.parse(new Notification(BuiltInQPChangeNotification.TYPE)); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridgeTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridgeTest.java deleted file mode 100644 index 7d0791b2007..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileDefinitionsBridgeTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.Arrays; -import org.junit.Test; -import org.sonar.api.profiles.ProfileDefinition; -import org.sonar.api.profiles.RulesProfile; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.rule.Severity; -import org.sonar.api.rules.ActiveRule; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.RuleParam; -import org.sonar.api.rules.RulePriority; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInActiveRule; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile; -import org.sonar.api.utils.ValidationMessages; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.groups.Tuple.tuple; - -public class BuiltInQProfileDefinitionsBridgeTest { - - @Test - public void noProfileDefinitions() { - BuiltInQProfileDefinitionsBridge bridge = new BuiltInQProfileDefinitionsBridge(); - - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - bridge.define(context); - - assertThat(context.profilesByLanguageAndName()).isEmpty(); - } - - @Test - public void bridgeProfileDefinitions() { - BuiltInQProfileDefinitionsBridge bridge = new BuiltInQProfileDefinitionsBridge(new Profile1(), new NullProfile(), new ProfileWithError()); - - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - bridge.define(context); - - assertThat(context.profilesByLanguageAndName()).hasSize(1); - assertThat(context.profilesByLanguageAndName().get("xoo")).hasSize(1); - - BuiltInQualityProfile profile1 = context.profile("xoo", "Profile 1"); - assertThat(profile1).isNotNull(); - assertThat(profile1.rules()).hasSize(3); - BuiltInActiveRule defaultSeverity = profile1.rule(RuleKey.of("repo1", "defaultSeverity")); - assertThat(defaultSeverity).isNotNull(); - assertThat(defaultSeverity.overriddenSeverity()).isNull(); - assertThat(defaultSeverity.overriddenParams()).isEmpty(); - - assertThat(profile1.rule(RuleKey.of("repo1", "overrideSeverity")).overriddenSeverity()).isEqualTo(Severity.CRITICAL); - - assertThat(profile1.rule(RuleKey.of("repo1", "overrideParam")).overriddenParams()) - .extracting(BuiltInQualityProfilesDefinition.OverriddenParam::key, BuiltInQualityProfilesDefinition.OverriddenParam::overriddenValue).containsOnly(tuple("param", "value")); - } - - private class Profile1 extends ProfileDefinition { - @Override - public RulesProfile createProfile(ValidationMessages validation) { - RulesProfile profile1 = RulesProfile.create("Profile 1", "xoo"); - - profile1.activateRule(Rule.create("repo1", "defaultSeverity"), null); - profile1.activateRule(Rule.create("repo1", "overrideSeverity"), RulePriority.CRITICAL); - Rule ruleWithParam = Rule.create("repo1", "overrideParam"); - ruleWithParam.setParams(Arrays.asList(new RuleParam(ruleWithParam, "param", "", ""))); - ActiveRule arWithParam = profile1.activateRule(ruleWithParam, null); - arWithParam.setParameter("param", "value"); - - return profile1; - } - } - - private class NullProfile extends ProfileDefinition { - @Override - public RulesProfile createProfile(ValidationMessages validation) { - return null; - } - } - - private class ProfileWithError extends ProfileDefinition { - @Override - public RulesProfile createProfile(ValidationMessages validation) { - validation.addErrorText("Foo"); - return RulesProfile.create("Profile with errors", "xoo"); - } - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImplTest.java deleted file mode 100644 index c5e0685517c..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImplTest.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.List; -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; -import org.sonar.api.rule.Severity; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile; -import org.sonar.api.utils.System2; -import org.sonar.core.util.SequenceUuidFactory; -import org.sonar.core.util.UuidFactory; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.qualityprofile.ActiveRuleDto; -import org.sonar.db.qualityprofile.ActiveRuleKey; -import org.sonar.db.qualityprofile.ActiveRuleParamDto; -import org.sonar.db.qualityprofile.QProfileChangeDto; -import org.sonar.db.qualityprofile.QProfileChangeQuery; -import org.sonar.db.qualityprofile.QProfileDto; -import org.sonar.db.rule.RuleDefinitionDto; -import org.sonar.server.language.LanguageTesting; -import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; -import org.sonar.server.util.TypeValidations; - -import static java.util.Collections.emptyList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -public class BuiltInQProfileInsertImplTest { - - @Rule - public BuiltInQProfileRepositoryRule builtInQProfileRepository = new BuiltInQProfileRepositoryRule(); - @Rule - public DbTester db = DbTester.create().setDisableDefaultOrganization(true); - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - private System2 system2 = new AlwaysIncreasingSystem2(); - private UuidFactory uuidFactory = new SequenceUuidFactory(); - private TypeValidations typeValidations = new TypeValidations(emptyList()); - private DbSession dbSession = db.getSession(); - private DbSession batchDbSession = db.getDbClient().openSession(true); - private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class); - private BuiltInQProfileInsertImpl underTest = new BuiltInQProfileInsertImpl(db.getDbClient(), system2, uuidFactory, typeValidations, activeRuleIndexer); - - @After - public void tearDown() { - batchDbSession.close(); - } - - @Test - public void insert_single_row_in_RULES_PROFILES_and_reference_it_in_ORG_QPROFILES() { - OrganizationDto org1 = db.organizations().insert(); - OrganizationDto org2 = db.organizations().insert(); - BuiltInQProfile builtIn = builtInQProfileRepository.create(LanguageTesting.newLanguage("xoo"), "the name", false); - - call(builtIn); - - verifyTableSize("org_qprofiles", 2); - verifyTableSize("rules_profiles", 1); - verifyTableSize("active_rules", 0); - verifyTableSize("active_rule_parameters", 0); - verifyTableSize("qprofile_changes", 0); - verifyTableSize("project_qprofiles", 0); - - QProfileDto profileOnOrg1 = verifyProfileInDb(org1, builtIn); - QProfileDto profileOnOrg2 = verifyProfileInDb(org2, builtIn); - - // same row in table rules_profiles is used - assertThat(profileOnOrg1.getKee()).isNotEqualTo(profileOnOrg2.getKee()); - assertThat(profileOnOrg1.getRulesProfileUuid()).isEqualTo(profileOnOrg2.getRulesProfileUuid()); - assertThat(profileOnOrg1.getId()).isEqualTo(profileOnOrg2.getId()); - } - - @Test - public void insert_active_rules_and_changelog() { - OrganizationDto org = db.organizations().insert(); - RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo")); - RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo")); - - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo"); - - newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL); - newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR); - newQp.done(); - - BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name"), rule1, rule2); - call(builtIn); - - verifyTableSize("rules_profiles", 1); - verifyTableSize("active_rules", 2); - verifyTableSize("active_rule_parameters", 0); - verifyTableSize("qprofile_changes", 2); - - QProfileDto profile = verifyProfileInDb(org, builtIn); - verifyActiveRuleInDb(profile, rule1, Severity.CRITICAL); - verifyActiveRuleInDb(profile, rule2, Severity.MAJOR); - } - - @Test - public void flag_profile_as_default_on_organization_if_declared_as_default_by_api() { - OrganizationDto org = db.organizations().insert(); - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(true); - newQp.done(); - - BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name")); - - call(builtIn); - - QProfileDto profile = verifyProfileInDb(org, builtIn); - QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, org, "xoo"); - assertThat(defaultProfile.getKee()).isEqualTo(profile.getKee()); - } - - @Test - public void existing_default_profile_in_organization_must_not_be_changed() { - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(true); - newQp.done(); - BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name")); - - OrganizationDto org = db.organizations().insert(); - QProfileDto currentDefault = db.qualityProfiles().insert(org, p -> p.setLanguage("xoo")); - db.qualityProfiles().setAsDefault(currentDefault); - - call(builtIn); - - QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, org, "xoo"); - assertThat(defaultProfile.getKee()).isEqualTo(currentDefault.getKee()); - } - - @Test - public void dont_flag_profile_as_default_on_organization_if_not_declared_as_default_by_api() { - OrganizationDto org = db.organizations().insert(); - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("the name", "xoo").setDefault(false); - newQp.done(); - BuiltInQProfile builtIn = builtInQProfileRepository.create(context.profile("xoo", "the name")); - - call(builtIn); - - QProfileDto defaultProfile = db.getDbClient().qualityProfileDao().selectDefaultProfile(dbSession, org, "xoo"); - assertThat(defaultProfile).isNull(); - } - - // TODO test params - // TODO test lot of active_rules, params, orgas - - private void verifyActiveRuleInDb(QProfileDto profile, RuleDefinitionDto rule, String expectedSeverity) { - ActiveRuleDto activeRule = db.getDbClient().activeRuleDao().selectByKey(dbSession, ActiveRuleKey.of(profile, rule.getKey())).get(); - assertThat(activeRule.getId()).isPositive(); - assertThat(activeRule.getInheritance()).isNull(); - assertThat(activeRule.doesOverride()).isFalse(); - assertThat(activeRule.getRuleId()).isEqualTo(rule.getId()); - assertThat(activeRule.getProfileId()).isEqualTo(profile.getId()); - assertThat(activeRule.getSeverityString()).isEqualTo(expectedSeverity); - assertThat(activeRule.getCreatedAt()).isPositive(); - assertThat(activeRule.getUpdatedAt()).isPositive(); - - List<ActiveRuleParamDto> params = db.getDbClient().activeRuleDao().selectParamsByActiveRuleId(dbSession, activeRule.getId()); - assertThat(params).isEmpty(); - - QProfileChangeQuery changeQuery = new QProfileChangeQuery(profile.getKee()); - QProfileChangeDto change = db.getDbClient().qProfileChangeDao().selectByQuery(dbSession, changeQuery).stream() - .filter(c -> c.getDataAsMap().get("ruleId").equals(String.valueOf(rule.getId()))) - .findFirst() - .get(); - assertThat(change.getChangeType()).isEqualTo(ActiveRuleChange.Type.ACTIVATED.name()); - assertThat(change.getCreatedAt()).isPositive(); - assertThat(change.getUuid()).isNotEmpty(); - assertThat(change.getUserUuid()).isNull(); - assertThat(change.getRulesProfileUuid()).isEqualTo(profile.getRulesProfileUuid()); - assertThat(change.getDataAsMap().get("severity")).isEqualTo(expectedSeverity); - } - - private QProfileDto verifyProfileInDb(OrganizationDto organization, BuiltInQProfile builtIn) { - QProfileDto profileOnOrg1 = db.getDbClient().qualityProfileDao().selectByNameAndLanguage(dbSession, organization, builtIn.getName(), builtIn.getLanguage()); - assertThat(profileOnOrg1.getLanguage()).isEqualTo(builtIn.getLanguage()); - assertThat(profileOnOrg1.getName()).isEqualTo(builtIn.getName()); - assertThat(profileOnOrg1.getOrganizationUuid()).isEqualTo(organization.getUuid()); - assertThat(profileOnOrg1.getParentKee()).isNull(); - assertThat(profileOnOrg1.getLastUsed()).isNull(); - assertThat(profileOnOrg1.getUserUpdatedAt()).isNull(); - assertThat(profileOnOrg1.getRulesUpdatedAt()).isNotEmpty(); - assertThat(profileOnOrg1.getKee()).isNotEqualTo(profileOnOrg1.getRulesProfileUuid()); - assertThat(profileOnOrg1.getId()).isNotNull(); - return profileOnOrg1; - } - - private void verifyTableSize(String table, int expectedSize) { - assertThat(db.countRowsOfTable(dbSession, table)).as("table " + table).isEqualTo(expectedSize); - } - - private void call(BuiltInQProfile builtIn) { - underTest.create(dbSession, batchDbSession, builtIn); - dbSession.commit(); - batchDbSession.commit(); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileLoaderTest.java deleted file mode 100644 index 5643fada891..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileLoaderTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import org.junit.Rule; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class BuiltInQProfileLoaderTest { - @Rule - public BuiltInQProfileRepositoryRule builtInQProfileRepositoryRule = new BuiltInQProfileRepositoryRule(); - - private BuiltInQProfileLoader underTest = new BuiltInQProfileLoader(builtInQProfileRepositoryRule); - - @Test - public void start_initializes_DefinedQProfileRepository() { - underTest.start(); - - assertThat(builtInQProfileRepositoryRule.isInitialized()).isTrue(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImplTest.java deleted file mode 100644 index 7a7d1a9373e..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryImplTest.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.resources.Language; -import org.sonar.api.resources.Languages; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.server.language.LanguageTesting; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.groups.Tuple.tuple; -import static org.mockito.Mockito.mock; - -public class BuiltInQProfileRepositoryImplTest { - private static final Language FOO_LANGUAGE = LanguageTesting.newLanguage("foo", "foo", "foo"); - private static final String SONAR_WAY_QP_NAME = "Sonar way"; - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private DbClient dbClient = dbTester.getDbClient(); - - @Test - public void get_throws_ISE_if_called_before_initialize() { - BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), new Languages()); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("initialize must be called first"); - - underTest.get(); - } - - @Test - public void initialize_throws_ISE_if_called_twice() { - BuiltInQProfileRepositoryImpl underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), new Languages()); - underTest.initialize(); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("initialize must be called only once"); - - underTest.initialize(); - } - - @Test - public void initialize_throws_ISE_if_language_has_no_builtin_qp() { - BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), new Languages(FOO_LANGUAGE)); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("The following languages have no built-in quality profiles: foo"); - - underTest.initialize(); - } - - @Test - public void initialize_creates_no_BuiltInQProfile_when_all_definitions_apply_to_non_defined_languages() { - BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(mock(DbClient.class), new Languages(), new DummyProfileDefinition("foo", "P1", false)); - - underTest.initialize(); - - assertThat(underTest.get()).isEmpty(); - } - - @Test - public void initialize_makes_single_profile_of_a_language_default_even_if_not_flagged_as_so() { - BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, new Languages(FOO_LANGUAGE), new DummyProfileDefinition("foo", "foo1", false)); - - underTest.initialize(); - - assertThat(underTest.get()) - .extracting(BuiltInQProfile::getLanguage, BuiltInQProfile::isDefault) - .containsExactly(tuple(FOO_LANGUAGE.getKey(), true)); - } - - @Test - public void initialize_makes_single_profile_of_a_language_default_even_if_flagged_as_so() { - BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, new Languages(FOO_LANGUAGE), new DummyProfileDefinition("foo", "foo1", true)); - - underTest.initialize(); - - assertThat(underTest.get()) - .extracting(BuiltInQProfile::getLanguage, BuiltInQProfile::isDefault) - .containsExactly(tuple(FOO_LANGUAGE.getKey(), true)); - } - - @Test - public void initialize_makes_first_profile_of_a_language_default_when_none_flagged_as_so() { - List<DummyProfileDefinition> definitions = new ArrayList<>( - asList(new DummyProfileDefinition("foo", "foo1", false), new DummyProfileDefinition("foo", "foo2", false))); - Collections.shuffle(definitions); - String firstName = definitions.get(0).getName(); - String secondName = definitions.get(1).getName(); - BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, new Languages(FOO_LANGUAGE), definitions.toArray(new BuiltInQualityProfilesDefinition[0])); - - underTest.initialize(); - - assertThat(underTest.get()) - .extracting(BuiltInQProfile::getName, BuiltInQProfile::isDefault) - .containsExactlyInAnyOrder(tuple(firstName, true), tuple(secondName, false)); - } - - @Test - public void initialize_fails_with_ISE_when_two_profiles_with_different_name_are_default_for_the_same_language() { - BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl(dbClient, new Languages(FOO_LANGUAGE), - new DummyProfileDefinition("foo", "foo1", true), new DummyProfileDefinition("foo", "foo2", true)); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Several Quality profiles are flagged as default for the language foo: [foo1, foo2]"); - - underTest.initialize(); - } - - @Test - public void initialize_creates_profile_Sonar_Way_as_default_if_none_other_is_defined_default_for_a_given_language() { - BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl( - dbClient, new Languages(FOO_LANGUAGE), - new DummyProfileDefinition("foo", "doh", false), new DummyProfileDefinition("foo", "boo", false), - new DummyProfileDefinition("foo", SONAR_WAY_QP_NAME, false), new DummyProfileDefinition("foo", "goo", false)); - - underTest.initialize(); - - assertThat(underTest.get()) - .filteredOn(b -> FOO_LANGUAGE.getKey().equals(b.getLanguage())) - .filteredOn(BuiltInQProfile::isDefault) - .extracting(BuiltInQProfile::getName) - .containsExactly(SONAR_WAY_QP_NAME); - } - - @Test - public void initialize_does_not_create_Sonar_Way_as_default_if_other_profile_is_defined_as_default() { - BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl( - dbClient, new Languages(FOO_LANGUAGE), - new DummyProfileDefinition("foo", SONAR_WAY_QP_NAME, false), new DummyProfileDefinition("foo", "goo", true)); - - underTest.initialize(); - - assertThat(underTest.get()) - .filteredOn(b -> FOO_LANGUAGE.getKey().equals(b.getLanguage())) - .filteredOn(BuiltInQProfile::isDefault) - .extracting(BuiltInQProfile::getName) - .containsExactly("goo"); - } - - @Test - public void initialize_matches_Sonar_Way_default_with_case_sensitivity() { - String sonarWayInOtherCase = SONAR_WAY_QP_NAME.toUpperCase(); - BuiltInQProfileRepository underTest = new BuiltInQProfileRepositoryImpl( - dbClient, new Languages(FOO_LANGUAGE), - new DummyProfileDefinition("foo", "goo", false), new DummyProfileDefinition("foo", sonarWayInOtherCase, false)); - - underTest.initialize(); - - assertThat(underTest.get()) - .filteredOn(b -> FOO_LANGUAGE.getKey().equals(b.getLanguage())) - .filteredOn(BuiltInQProfile::isDefault) - .extracting(BuiltInQProfile::getName) - .containsExactly("goo"); - } - - private static final class DummyProfileDefinition implements BuiltInQualityProfilesDefinition { - private final String language; - private final String name; - private final boolean defaultProfile; - - private DummyProfileDefinition(String language, String name, boolean defaultProfile) { - this.language = language; - this.name = name; - this.defaultProfile = defaultProfile; - } - - @Override - public void define(Context context) { - context.createBuiltInQualityProfile(name, language) - .setDefault(defaultProfile).done(); - } - - String getName() { - return name; - } - - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryRule.java deleted file mode 100644 index f0f481c55e2..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileRepositoryRule.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import org.junit.rules.ExternalResource; -import org.sonar.api.resources.Language; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.rule.RuleDefinitionDto; - -import static com.google.common.base.Preconditions.checkState; - -public class BuiltInQProfileRepositoryRule extends ExternalResource implements BuiltInQProfileRepository { - private boolean initializeCalled = false; - private List<BuiltInQProfile> profiles = new ArrayList<>(); - - @Override - protected void before() { - this.initializeCalled = false; - this.profiles.clear(); - } - - @Override - public void initialize() { - checkState(!initializeCalled, "initialize must be called only once"); - this.initializeCalled = true; - } - - @Override - public List<BuiltInQProfile> get() { - checkState(initializeCalled, "initialize must be called first"); - - return ImmutableList.copyOf(profiles); - } - - public boolean isInitialized() { - return initializeCalled; - } - - public BuiltInQProfile add(Language language, String profileName) { - return add(language, profileName, false); - } - - public BuiltInQProfile add(Language language, String profileName, boolean isDefault) { - return add(language, profileName, isDefault, new BuiltInQProfile.ActiveRule[0]); - } - - public BuiltInQProfile add(Language language, String profileName, boolean isDefault, BuiltInQProfile.ActiveRule... rules) { - BuiltInQProfile builtIn = create(language, profileName, isDefault, rules); - profiles.add(builtIn); - return builtIn; - } - - public BuiltInQProfile create(Language language, String profileName, boolean isDefault, BuiltInQProfile.ActiveRule... rules) { - BuiltInQProfile.Builder builder = new BuiltInQProfile.Builder() - .setLanguage(language.getKey()) - .setName(profileName) - .setDeclaredDefault(isDefault); - Arrays.stream(rules).forEach(builder::addRule); - return builder.build(); - } - - public BuiltInQProfile create(BuiltInQualityProfilesDefinition.BuiltInQualityProfile api, RuleDefinitionDto... rules) { - BuiltInQProfile.Builder builder = new BuiltInQProfile.Builder() - .setLanguage(api.language()) - .setName(api.name()) - .setDeclaredDefault(api.isDefault()); - Map<RuleKey, RuleDefinitionDto> rulesByRuleKey = Arrays.stream(rules) - .collect(MoreCollectors.uniqueIndex(RuleDefinitionDto::getKey)); - api.rules().forEach(rule -> { - RuleKey ruleKey = RuleKey.of(rule.repoKey(), rule.ruleKey()); - RuleDefinitionDto ruleDefinition = rulesByRuleKey.get(ruleKey); - Preconditions.checkState(ruleDefinition != null, "Rule '%s' not found", ruleKey); - builder.addRule(rule, ruleDefinition.getId()); - }); - return builder - .build(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImplTest.java deleted file mode 100644 index ee0f3fbfae8..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImplTest.java +++ /dev/null @@ -1,449 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import javax.annotation.Nullable; -import org.assertj.core.groups.Tuple; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.rule.Severity; -import org.sonar.api.rules.RulePriority; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile; -import org.sonar.api.utils.System2; -import org.sonar.api.impl.utils.TestSystem2; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.qualityprofile.ActiveRuleDto; -import org.sonar.db.qualityprofile.ActiveRuleParamDto; -import org.sonar.db.qualityprofile.OrgActiveRuleDto; -import org.sonar.db.qualityprofile.QProfileDto; -import org.sonar.db.qualityprofile.RulesProfileDto; -import org.sonar.db.rule.RuleDefinitionDto; -import org.sonar.db.rule.RuleParamDto; -import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; -import org.sonar.server.tester.UserSessionRule; -import org.sonar.server.util.IntegerTypeValidation; -import org.sonar.server.util.StringTypeValidation; -import org.sonar.server.util.TypeValidations; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.groups.Tuple.tuple; -import static org.mockito.Mockito.mock; -import static org.sonar.api.rules.RulePriority.BLOCKER; -import static org.sonar.api.rules.RulePriority.CRITICAL; -import static org.sonar.api.rules.RulePriority.MAJOR; -import static org.sonar.api.rules.RulePriority.MINOR; -import static org.sonar.db.qualityprofile.QualityProfileTesting.newRuleProfileDto; -import static org.sonar.server.qualityprofile.ActiveRuleInheritance.INHERITED; - -public class BuiltInQProfileUpdateImplTest { - - private static final long NOW = 1_000; - private static final long PAST = NOW - 100; - - @Rule - public BuiltInQProfileRepositoryRule builtInProfileRepository = new BuiltInQProfileRepositoryRule(); - @Rule - public DbTester db = DbTester.create(); - @Rule - public UserSessionRule userSession = UserSessionRule.standalone(); - private System2 system2 = new TestSystem2().setNow(NOW); - private ActiveRuleIndexer activeRuleIndexer = mock(ActiveRuleIndexer.class); - private TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation())); - private RuleActivator ruleActivator = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession); - - private BuiltInQProfileUpdateImpl underTest = new BuiltInQProfileUpdateImpl(db.getDbClient(), ruleActivator, activeRuleIndexer); - - private RulesProfileDto persistedProfile; - - @Before - public void setUp() { - persistedProfile = newRuleProfileDto(rp -> rp - .setIsBuiltIn(true) - .setLanguage("xoo") - .setRulesUpdatedAt(null)); - db.getDbClient().qualityProfileDao().insert(db.getSession(), persistedProfile); - db.commit(); - } - - @Test - public void activate_new_rules() { - RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo")); - RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo")); - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo"); - newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL); - newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR); - newQp.done(); - BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule1, rule2); - - underTest.update(db.getSession(), builtIn, persistedProfile); - - List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); - assertThat(activeRules).hasSize(2); - assertThatRuleIsNewlyActivated(activeRules, rule1, CRITICAL); - assertThatRuleIsNewlyActivated(activeRules, rule2, MAJOR); - assertThatProfileIsMarkedAsUpdated(persistedProfile); - } - - @Test - public void already_activated_rule_is_updated_in_case_of_differences() { - RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo")); - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo"); - newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()).overrideSeverity(Severity.CRITICAL); - newQp.done(); - BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule); - - activateRuleInDb(persistedProfile, rule, BLOCKER); - - underTest.update(db.getSession(), builtIn, persistedProfile); - - List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); - assertThat(activeRules).hasSize(1); - assertThatRuleIsUpdated(activeRules, rule, CRITICAL); - assertThatProfileIsMarkedAsUpdated(persistedProfile); - } - - @Test - public void already_activated_rule_is_not_touched_if_no_differences() { - RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo")); - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo"); - newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()).overrideSeverity(Severity.CRITICAL); - newQp.done(); - BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule); - - activateRuleInDb(persistedProfile, rule, CRITICAL); - - underTest.update(db.getSession(), builtIn, persistedProfile); - - List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); - assertThat(activeRules).hasSize(1); - assertThatRuleIsUntouched(activeRules, rule, CRITICAL); - assertThatProfileIsNotMarkedAsUpdated(persistedProfile); - } - - @Test - public void deactivate_rule_that_is_not_in_built_in_definition_anymore() { - RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo")); - RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo")); - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo"); - newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR); - newQp.done(); - BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule1, rule2); - - // built-in definition contains only rule2 - // so rule1 must be deactivated - activateRuleInDb(persistedProfile, rule1, CRITICAL); - - underTest.update(db.getSession(), builtIn, persistedProfile); - - List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); - assertThat(activeRules).hasSize(1); - assertThatRuleIsDeactivated(activeRules, rule1); - assertThatProfileIsMarkedAsUpdated(persistedProfile); - } - - @Test - public void activate_deactivate_and_update_three_rules_at_the_same_time() { - RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("xoo")); - RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("xoo")); - RuleDefinitionDto rule3 = db.rules().insert(r -> r.setLanguage("xoo")); - - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo"); - newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey()).overrideSeverity(Severity.CRITICAL); - newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey()).overrideSeverity(Severity.MAJOR); - newQp.done(); - BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule1, rule2); - - // rule1 must be updated (blocker to critical) - // rule2 must be activated - // rule3 must be deactivated - activateRuleInDb(persistedProfile, rule1, BLOCKER); - activateRuleInDb(persistedProfile, rule3, BLOCKER); - - underTest.update(db.getSession(), builtIn, persistedProfile); - - List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); - assertThat(activeRules).hasSize(2); - assertThatRuleIsUpdated(activeRules, rule1, CRITICAL); - assertThatRuleIsNewlyActivated(activeRules, rule2, MAJOR); - assertThatRuleIsDeactivated(activeRules, rule3); - assertThatProfileIsMarkedAsUpdated(persistedProfile); - } - - // SONAR-10473 - @Test - public void activate_rule_on_built_in_profile_resets_severity_to_default_if_not_overridden() { - RuleDefinitionDto rule = db.rules().insert(r -> r.setSeverity(Severity.MAJOR).setLanguage("xoo")); - - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", "xoo"); - newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()); - newQp.done(); - BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile("xoo", "Sonar way"), rule); - underTest.update(db.getSession(), builtIn, persistedProfile); - - List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); - assertThatRuleIsNewlyActivated(activeRules, rule, MAJOR); - - // emulate an upgrade of analyzer that changes the default severity of the rule - rule.setSeverity(Severity.MINOR); - db.rules().update(rule); - - underTest.update(db.getSession(), builtIn, persistedProfile); - activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); - assertThatRuleIsNewlyActivated(activeRules, rule, MINOR); - } - - @Test - public void activate_rule_on_built_in_profile_resets_params_to_default_if_not_overridden() { - RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo")); - RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setName("min").setDefaultValue("10")); - - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile("Sonar way", rule.getLanguage()); - newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()); - newQp.done(); - BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(newQp.language(), newQp.name()), rule); - underTest.update(db.getSession(), builtIn, persistedProfile); - - List<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); - assertThat(activeRules).hasSize(1); - assertThatRuleHasParams(db, activeRules.get(0), tuple("min", "10")); - - // emulate an upgrade of analyzer that changes the default value of parameter min - ruleParam.setDefaultValue("20"); - db.getDbClient().ruleDao().updateRuleParam(db.getSession(), rule, ruleParam); - - underTest.update(db.getSession(), builtIn, persistedProfile); - activeRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), persistedProfile); - assertThat(activeRules).hasSize(1); - assertThatRuleHasParams(db, activeRules.get(0), tuple("min", "20")); - } - - @Test - public void propagate_activation_to_descendant_profiles() { - RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo")); - - QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization(), - p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true)); - QProfileDto childProfile = createChildProfile(profile); - QProfileDto grandchildProfile = createChildProfile(childProfile); - - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage()); - newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()); - newQp.done(); - BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule); - List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile)); - - assertThat(changes).hasSize(3); - assertThatRuleIsActivated(profile, rule, changes, rule.getSeverityString(), null, emptyMap()); - assertThatRuleIsActivated(childProfile, rule, changes, rule.getSeverityString(), INHERITED, emptyMap()); - assertThatRuleIsActivated(grandchildProfile, rule, changes, rule.getSeverityString(), INHERITED, emptyMap()); - } - - @Test - public void do_not_load_descendants_if_no_changes() { - RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo")); - - QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization(), - p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true)); - QProfileDto childProfile = createChildProfile(profile); - - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage()); - newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()); - newQp.done(); - - // first run - BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule); - List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile)); - assertThat(changes).hasSize(2).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.ACTIVATED); - - // second run, without any input changes - RuleActivator ruleActivatorWithoutDescendants = new RuleActivator(system2, db.getDbClient(), typeValidations, userSession) { - @Override - DescendantProfilesSupplier createDescendantProfilesSupplier(DbSession dbSession) { - return (profiles, ruleIds) -> { - throw new IllegalStateException("BOOM - descendants should not be loaded"); - }; - } - }; - changes = new BuiltInQProfileUpdateImpl(db.getDbClient(), ruleActivatorWithoutDescendants, activeRuleIndexer).update(db.getSession(), builtIn, RulesProfileDto.from(profile)); - assertThat(changes).isEmpty(); - } - - @Test - public void propagate_deactivation_to_descendant_profiles() { - RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("xoo")); - - QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization(), - p -> p.setLanguage(rule.getLanguage()).setIsBuiltIn(true)); - QProfileDto childProfile = createChildProfile(profile); - QProfileDto grandChildProfile = createChildProfile(childProfile); - - BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage()); - newQp.activateRule(rule.getRepositoryKey(), rule.getRuleKey()); - newQp.done(); - - // first run to activate the rule - BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule); - List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile)); - assertThat(changes).hasSize(3).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.ACTIVATED); - - // second run to deactivate the rule - context = new BuiltInQualityProfilesDefinition.Context(); - NewBuiltInQualityProfile updatedQp = context.createBuiltInQualityProfile(profile.getName(), profile.getLanguage()); - updatedQp.done(); - builtIn = builtInProfileRepository.create(context.profile(profile.getLanguage(), profile.getName()), rule); - changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(profile)); - assertThat(changes).hasSize(3).extracting(ActiveRuleChange::getType).containsOnly(ActiveRuleChange.Type.DEACTIVATED); - - assertThatRuleIsDeactivated(profile, rule); - assertThatRuleIsDeactivated(childProfile, rule); - assertThatRuleIsDeactivated(grandChildProfile, rule); - } - - private QProfileDto createChildProfile(QProfileDto parent) { - return db.qualityProfiles().insert(db.getDefaultOrganization(), p -> p - .setLanguage(parent.getLanguage()) - .setParentKee(parent.getKee()) - .setName("Child of " + parent.getName())) - .setIsBuiltIn(false); - } - - private void assertThatRuleIsActivated(QProfileDto profile, RuleDefinitionDto rule, @Nullable List<ActiveRuleChange> changes, - String expectedSeverity, @Nullable ActiveRuleInheritance expectedInheritance, Map<String, String> expectedParams) { - OrgActiveRuleDto activeRule = db.getDbClient().activeRuleDao().selectByProfile(db.getSession(), profile) - .stream() - .filter(ar -> ar.getRuleKey().equals(rule.getKey())) - .findFirst() - .orElseThrow(IllegalStateException::new); - - assertThat(activeRule.getSeverityString()).isEqualTo(expectedSeverity); - assertThat(activeRule.getInheritance()).isEqualTo(expectedInheritance != null ? expectedInheritance.name() : null); - assertThat(activeRule.getCreatedAt()).isNotNull(); - assertThat(activeRule.getUpdatedAt()).isNotNull(); - - List<ActiveRuleParamDto> params = db.getDbClient().activeRuleDao().selectParamsByActiveRuleId(db.getSession(), activeRule.getId()); - assertThat(params).hasSize(expectedParams.size()); - - if (changes != null) { - ActiveRuleChange change = changes.stream() - .filter(c -> c.getActiveRule().getId().equals(activeRule.getId())) - .findFirst().orElseThrow(IllegalStateException::new); - assertThat(change.getInheritance()).isEqualTo(expectedInheritance); - assertThat(change.getSeverity()).isEqualTo(expectedSeverity); - assertThat(change.getType()).isEqualTo(ActiveRuleChange.Type.ACTIVATED); - } - } - - private static void assertThatRuleHasParams(DbTester db, ActiveRuleDto activeRule, Tuple... expectedParams) { - assertThat(db.getDbClient().activeRuleDao().selectParamsByActiveRuleId(db.getSession(), activeRule.getId())) - .extracting(ActiveRuleParamDto::getKey, ActiveRuleParamDto::getValue) - .containsExactlyInAnyOrder(expectedParams); - } - - private static void assertThatRuleIsNewlyActivated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity) { - ActiveRuleDto activeRule = findRule(activeRules, rule).get(); - - assertThat(activeRule.getInheritance()).isNull(); - assertThat(activeRule.getSeverityString()).isEqualTo(severity.name()); - assertThat(activeRule.getCreatedAt()).isEqualTo(NOW); - assertThat(activeRule.getUpdatedAt()).isEqualTo(NOW); - } - - private static void assertThatRuleIsUpdated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity) { - ActiveRuleDto activeRule = findRule(activeRules, rule).get(); - - assertThat(activeRule.getInheritance()).isNull(); - assertThat(activeRule.getSeverityString()).isEqualTo(severity.name()); - assertThat(activeRule.getCreatedAt()).isEqualTo(PAST); - assertThat(activeRule.getUpdatedAt()).isEqualTo(NOW); - } - - private static void assertThatRuleIsUntouched(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule, RulePriority severity) { - ActiveRuleDto activeRule = findRule(activeRules, rule).get(); - - assertThat(activeRule.getInheritance()).isNull(); - assertThat(activeRule.getSeverityString()).isEqualTo(severity.name()); - assertThat(activeRule.getCreatedAt()).isEqualTo(PAST); - assertThat(activeRule.getUpdatedAt()).isEqualTo(PAST); - } - - private void assertThatRuleIsDeactivated(QProfileDto profile, RuleDefinitionDto rule) { - Collection<ActiveRuleDto> activeRules = db.getDbClient().activeRuleDao().selectByRulesAndRuleProfileUuids(db.getSession(), singletonList(rule.getId()), singletonList(profile.getRulesProfileUuid())); - assertThat(activeRules).isEmpty(); - } - - private static void assertThatRuleIsDeactivated(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule) { - assertThat(findRule(activeRules, rule)).isEmpty(); - } - - private void assertThatProfileIsMarkedAsUpdated(RulesProfileDto dto) { - RulesProfileDto reloaded = db.getDbClient().qualityProfileDao().selectBuiltInRuleProfiles(db.getSession()) - .stream() - .filter(p -> p.getKee().equals(dto.getKee())) - .findFirst() - .get(); - assertThat(reloaded.getRulesUpdatedAt()).isNotEmpty(); - } - - private void assertThatProfileIsNotMarkedAsUpdated(RulesProfileDto dto) { - RulesProfileDto reloaded = db.getDbClient().qualityProfileDao().selectBuiltInRuleProfiles(db.getSession()) - .stream() - .filter(p -> p.getKee().equals(dto.getKee())) - .findFirst() - .get(); - assertThat(reloaded.getRulesUpdatedAt()).isNull(); - } - - private static Optional<ActiveRuleDto> findRule(List<ActiveRuleDto> activeRules, RuleDefinitionDto rule) { - return activeRules.stream() - .filter(ar -> ar.getRuleKey().equals(rule.getKey())) - .findFirst(); - } - - private void activateRuleInDb(RulesProfileDto profile, RuleDefinitionDto rule, RulePriority severity) { - ActiveRuleDto dto = new ActiveRuleDto() - .setProfileId(profile.getId()) - .setSeverity(severity.name()) - .setRuleId(rule.getId()) - .setCreatedAt(PAST) - .setUpdatedAt(PAST); - db.getDbClient().activeRuleDao().insert(db.getSession(), dto); - db.commit(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQualityProfilesUpdateListenerTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQualityProfilesUpdateListenerTest.java deleted file mode 100644 index d796ff9e6fd..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQualityProfilesUpdateListenerTest.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.qualityprofile; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; -import java.util.Random; -import org.assertj.core.groups.Tuple; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.notifications.Notification; -import org.sonar.api.resources.Language; -import org.sonar.api.resources.Languages; -import org.sonar.db.qualityprofile.ActiveRuleKey; -import org.sonar.db.rule.RuleDefinitionDto; -import org.sonar.server.notification.NotificationManager; -import org.sonar.server.qualityprofile.BuiltInQPChangeNotificationBuilder.Profile; - -import static java.util.Arrays.asList; -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.sonar.core.config.CorePropertyDefinitions.DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES; -import static org.sonar.server.language.LanguageTesting.newLanguage; -import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED; -import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.DEACTIVATED; -import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.UPDATED; - -public class BuiltInQualityProfilesUpdateListenerTest { - - private NotificationManager notificationManager = mock(NotificationManager.class); - private MapSettings settings = new MapSettings(); - - @Test - public void add_profile_to_notification_for_added_rules() { - enableNotificationInGlobalSettings(); - Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create(); - Languages languages = new Languages(); - Tuple expectedTuple = addProfile(profiles, languages, ACTIVATED); - - BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig()); - underTest.onChange(profiles, 0, 1); - - ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class); - verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture()); - verifyNoMoreInteractions(notificationManager); - assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles()) - .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules) - .containsExactlyInAnyOrder(expectedTuple); - } - - @Test - public void add_profile_to_notification_for_updated_rules() { - enableNotificationInGlobalSettings(); - Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create(); - Languages languages = new Languages(); - Tuple expectedTuple = addProfile(profiles, languages, UPDATED); - - BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig()); - underTest.onChange(profiles, 0, 1); - - ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class); - verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture()); - verifyNoMoreInteractions(notificationManager); - assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles()) - .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getUpdatedRules) - .containsExactlyInAnyOrder(expectedTuple); - } - - @Test - public void add_profile_to_notification_for_removed_rules() { - enableNotificationInGlobalSettings(); - Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create(); - Languages languages = new Languages(); - Tuple expectedTuple = addProfile(profiles, languages, DEACTIVATED); - - BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig()); - underTest.onChange(profiles, 0, 1); - - ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class); - verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture()); - verifyNoMoreInteractions(notificationManager); - assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles()) - .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getRemovedRules) - .containsExactlyInAnyOrder(expectedTuple); - } - - @Test - public void add_multiple_profiles_to_notification() { - enableNotificationInGlobalSettings(); - Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create(); - Languages languages = new Languages(); - Tuple expectedTuple1 = addProfile(profiles, languages, ACTIVATED); - Tuple expectedTuple2 = addProfile(profiles, languages, ACTIVATED); - - BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig()); - underTest.onChange(profiles, 0, 1); - - ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class); - verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture()); - verifyNoMoreInteractions(notificationManager); - assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles()) - .extracting(Profile::getProfileName, Profile::getLanguageKey, Profile::getLanguageName, Profile::getNewRules) - .containsExactlyInAnyOrder(expectedTuple1, expectedTuple2); - } - - @Test - public void add_start_and_end_dates_to_notification() { - enableNotificationInGlobalSettings(); - Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create(); - Languages languages = new Languages(); - addProfile(profiles, languages, ACTIVATED); - long startDate = 10_000_000_000L; - long endDate = 15_000_000_000L; - - BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig()); - underTest.onChange(profiles, startDate, endDate); - - ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(Notification.class); - verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture()); - verifyNoMoreInteractions(notificationManager); - assertThat(BuiltInQPChangeNotificationBuilder.parse(notificationArgumentCaptor.getValue()).getProfiles()) - .extracting(Profile::getStartDate, Profile::getEndDate) - .containsExactlyInAnyOrder(tuple(startDate, endDate)); - } - - @Test - public void avoid_notification_if_configured_in_settings() { - settings.setProperty(DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES, true); - Multimap<QProfileName, ActiveRuleChange> profiles = ArrayListMultimap.create(); - Languages languages = new Languages(); - addProfile(profiles, languages, ACTIVATED); - - BuiltInQualityProfilesUpdateListener underTest = new BuiltInQualityProfilesUpdateListener(notificationManager, languages, settings.asConfig()); - underTest.onChange(profiles, 0, 1); - - verifyZeroInteractions(notificationManager); - } - - private Tuple addProfile(Multimap<QProfileName, ActiveRuleChange> profiles, Languages languages, ActiveRuleChange.Type type) { - String profileName = randomLowerCaseText(); - int ruleId1 = new Random().nextInt(952); - int ruleId2 = new Random().nextInt(952); - Language language = newLanguage(randomLowerCaseText(), randomLowerCaseText()); - languages.add(language); - profiles.putAll(new QProfileName(language.getKey(), profileName), - asList(new ActiveRuleChange( - type, - ActiveRuleKey.parse("qp:repo:rule1"), new RuleDefinitionDto().setId(ruleId1)), - new ActiveRuleChange(type, ActiveRuleKey.parse("qp:repo:rule2"), new RuleDefinitionDto().setId(ruleId2)))); - return tuple(profileName, language.getKey(), language.getName(), 2); - } - - private static String randomLowerCaseText() { - return randomAlphanumeric(20).toLowerCase(); - } - - private void enableNotificationInGlobalSettings() { - settings.setProperty(DISABLE_NOTIFICATION_ON_BUILT_IN_QPROFILES, false); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/AbstractMockUserSession.java b/server/sonar-server/src/test/java/org/sonar/server/tester/AbstractMockUserSession.java deleted file mode 100644 index 26c8e103942..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/AbstractMockUserSession.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.tester; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableSet; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import org.sonar.api.web.UserRole; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.server.user.AbstractUserSession; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Maps.newHashMap; - -public abstract class AbstractMockUserSession<T extends AbstractMockUserSession> extends AbstractUserSession { - private static final Set<String> PUBLIC_PERMISSIONS = ImmutableSet.of(UserRole.USER, UserRole.CODEVIEWER); // FIXME to check with Simon - - private final Class<T> clazz; - private HashMultimap<String, String> projectUuidByPermission = HashMultimap.create(); - private final HashMultimap<String, OrganizationPermission> permissionsByOrganizationUuid = HashMultimap.create(); - private Map<String, String> projectUuidByComponentUuid = newHashMap(); - private Set<String> projectPermissions = new HashSet<>(); - private Set<String> organizationMembership = new HashSet<>(); - private boolean systemAdministrator = false; - - protected AbstractMockUserSession(Class<T> clazz) { - this.clazz = clazz; - } - - public T addPermission(OrganizationPermission permission, String organizationUuid) { - permissionsByOrganizationUuid.put(organizationUuid, permission); - return clazz.cast(this); - } - - @Override - protected boolean hasPermissionImpl(OrganizationPermission permission, String organizationUuid) { - return permissionsByOrganizationUuid.get(organizationUuid).contains(permission); - } - - /** - * Use this method to register public root component and non root components the UserSession must be aware of. - * (ie. this method can be used to emulate the content of the DB) - */ - public T registerComponents(ComponentDto... components) { - Arrays.stream(components) - .forEach(component -> { - if (component.projectUuid().equals(component.uuid()) && !component.isPrivate()) { - this.projectUuidByPermission.put(UserRole.USER, component.uuid()); - this.projectUuidByPermission.put(UserRole.CODEVIEWER, component.uuid()); - this.projectPermissions.add(UserRole.USER); - this.projectPermissions.add(UserRole.CODEVIEWER); - } - this.projectUuidByComponentUuid.put(component.uuid(), component.projectUuid()); - }); - return clazz.cast(this); - } - - public T addProjectPermission(String permission, ComponentDto... components) { - Arrays.stream(components).forEach(component -> { - checkArgument( - component.isPrivate() || !PUBLIC_PERMISSIONS.contains(permission), - "public component %s can't be granted public permission %s", component.uuid(), permission); - }); - registerComponents(components); - this.projectPermissions.add(permission); - Arrays.stream(components) - .forEach(component -> this.projectUuidByPermission.put(permission, component.projectUuid())); - return clazz.cast(this); - } - - @Override - protected Optional<String> componentUuidToProjectUuid(String componentUuid) { - return Optional.ofNullable(projectUuidByComponentUuid.get(componentUuid)); - } - - @Override - protected boolean hasProjectUuidPermission(String permission, String projectUuid) { - return projectPermissions.contains(permission) && projectUuidByPermission.get(permission).contains(projectUuid); - } - - public T setSystemAdministrator(boolean b) { - this.systemAdministrator = b; - return clazz.cast(this); - } - - @Override - public boolean isSystemAdministrator() { - return isRoot() || systemAdministrator; - } - - @Override - protected boolean hasMembershipImpl(OrganizationDto organizationDto) { - return organizationMembership.contains(organizationDto.getUuid()); - } - - public void addOrganizationMembership(OrganizationDto organization) { - this.organizationMembership.add(organization.getUuid()); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/AnonymousMockUserSession.java b/server/sonar-server/src/test/java/org/sonar/server/tester/AnonymousMockUserSession.java deleted file mode 100644 index 3c5d4068c61..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/AnonymousMockUserSession.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.tester; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.user.GroupDto; - -public class AnonymousMockUserSession extends AbstractMockUserSession<AnonymousMockUserSession> { - - public AnonymousMockUserSession() { - super(AnonymousMockUserSession.class); - } - - @Override - public boolean isRoot() { - return false; - } - - @Override - public String getLogin() { - return null; - } - - @Override public String getUuid() { - return null; - } - - @Override - public String getName() { - return null; - } - - @Override - public Integer getUserId() { - return null; - } - - @Override - public boolean isLoggedIn() { - return false; - } - - @Override - public Collection<GroupDto> getGroups() { - return Collections.emptyList(); - } - - @Override - public Optional<IdentityProvider> getIdentityProvider() { - return Optional.empty(); - } - - @Override - public Optional<ExternalIdentity> getExternalIdentity() { - return Optional.empty(); - } - - @Override - public boolean hasMembershipImpl(OrganizationDto organizationDto) { - return false; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/AttributeHolderServletContext.java b/server/sonar-server/src/test/java/org/sonar/server/tester/AttributeHolderServletContext.java deleted file mode 100644 index e65c0d35e67..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/AttributeHolderServletContext.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.tester; - -import com.google.common.collect.Maps; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Collections; -import java.util.Enumeration; -import java.util.EventListener; -import java.util.Map; -import java.util.Set; -import javax.servlet.Filter; -import javax.servlet.FilterRegistration; -import javax.servlet.RequestDispatcher; -import javax.servlet.Servlet; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletRegistration; -import javax.servlet.SessionCookieConfig; -import javax.servlet.SessionTrackingMode; -import javax.servlet.descriptor.JspConfigDescriptor; - -/** - * A dummy implementation of {@link ServletContext} which only implements the attribute related methods. All other - * methods thrown a {@link UnsupportedOperationException} when called. - */ -class AttributeHolderServletContext implements ServletContext { - @Override - public String getContextPath() { - throw new UnsupportedOperationException(); - } - - @Override - public ServletContext getContext(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public int getMajorVersion() { - throw new UnsupportedOperationException(); - } - - @Override - public int getMinorVersion() { - throw new UnsupportedOperationException(); - } - - @Override - public int getEffectiveMajorVersion() { - throw new UnsupportedOperationException(); - } - - @Override - public int getEffectiveMinorVersion() { - throw new UnsupportedOperationException(); - } - - @Override - public String getMimeType(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public Set<String> getResourcePaths(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public URL getResource(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public InputStream getResourceAsStream(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public RequestDispatcher getRequestDispatcher(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public RequestDispatcher getNamedDispatcher(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public Servlet getServlet(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public Enumeration<Servlet> getServlets() { - throw new UnsupportedOperationException(); - } - - @Override - public Enumeration<String> getServletNames() { - throw new UnsupportedOperationException(); - } - - @Override - public void log(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public void log(Exception e, String s) { - throw new UnsupportedOperationException(); - } - - @Override - public void log(String s, Throwable throwable) { - throw new UnsupportedOperationException(); - } - - @Override - public String getRealPath(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public String getServerInfo() { - throw new UnsupportedOperationException(); - } - - @Override - public String getInitParameter(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public Enumeration<String> getInitParameterNames() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean setInitParameter(String s, String s1) { - throw new UnsupportedOperationException(); - } - - @Override - public Object getAttribute(String s) { - return this.attributes.get(s); - } - - @Override - public Enumeration<String> getAttributeNames() { - return Collections.enumeration(this.attributes.keySet()); - } - - private final Map<String, Object> attributes = Maps.newHashMap(); - - @Override - public void setAttribute(String s, Object o) { - attributes.put(s, o); - } - - @Override - public void removeAttribute(String s) { - attributes.remove(s); - } - - @Override - public String getServletContextName() { - throw new UnsupportedOperationException(); - } - - @Override - public ServletRegistration.Dynamic addServlet(String s, String s1) { - throw new UnsupportedOperationException(); - } - - @Override - public ServletRegistration.Dynamic addServlet(String s, Servlet servlet) { - throw new UnsupportedOperationException(); - } - - @Override - public ServletRegistration.Dynamic addServlet(String s, Class<? extends Servlet> aClass) { - throw new UnsupportedOperationException(); - } - - @Override - public <T extends Servlet> T createServlet(Class<T> aClass) { - throw new UnsupportedOperationException(); - } - - @Override - public ServletRegistration getServletRegistration(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public Map<String, ? extends ServletRegistration> getServletRegistrations() { - throw new UnsupportedOperationException(); - } - - @Override - public FilterRegistration.Dynamic addFilter(String s, String s1) { - throw new UnsupportedOperationException(); - } - - @Override - public FilterRegistration.Dynamic addFilter(String s, Filter filter) { - throw new UnsupportedOperationException(); - } - - @Override - public FilterRegistration.Dynamic addFilter(String s, Class<? extends Filter> aClass) { - throw new UnsupportedOperationException(); - } - - @Override - public <T extends Filter> T createFilter(Class<T> aClass) { - throw new UnsupportedOperationException(); - } - - @Override - public FilterRegistration getFilterRegistration(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public Map<String, ? extends FilterRegistration> getFilterRegistrations() { - throw new UnsupportedOperationException(); - } - - @Override - public SessionCookieConfig getSessionCookieConfig() { - throw new UnsupportedOperationException(); - } - - @Override - public void setSessionTrackingModes(Set<SessionTrackingMode> set) { - throw new UnsupportedOperationException(); - } - - @Override - public Set<SessionTrackingMode> getDefaultSessionTrackingModes() { - throw new UnsupportedOperationException(); - } - - @Override - public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() { - throw new UnsupportedOperationException(); - } - - @Override - public void addListener(String s) { - throw new UnsupportedOperationException(); - } - - @Override - public <T extends EventListener> void addListener(T t) { - throw new UnsupportedOperationException(); - } - - @Override - public void addListener(Class<? extends EventListener> aClass) { - throw new UnsupportedOperationException(); - } - - @Override - public <T extends EventListener> T createListener(Class<T> aClass) { - throw new UnsupportedOperationException(); - } - - @Override - public JspConfigDescriptor getJspConfigDescriptor() { - throw new UnsupportedOperationException(); - } - - @Override - public ClassLoader getClassLoader() { - throw new UnsupportedOperationException(); - } - - @Override - public void declareRoles(String... strings) { - throw new UnsupportedOperationException(); - } - - @Override - public String getVirtualServerName() { - throw new UnsupportedOperationException(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/AttributeHolderServletContextTest.java b/server/sonar-server/src/test/java/org/sonar/server/tester/AttributeHolderServletContextTest.java deleted file mode 100644 index dc3935c4311..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/AttributeHolderServletContextTest.java +++ /dev/null @@ -1,373 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.tester; - -import com.google.common.collect.ImmutableSet; -import java.io.IOException; -import java.util.Collections; -import java.util.Enumeration; -import java.util.EventListener; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.Servlet; -import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.SessionTrackingMode; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class AttributeHolderServletContextTest { - public static final String SOME_STRING = "SOME_STRING"; - public static final Exception SOME_EXCEPTION = new Exception(); - public static final String SOME_OTHER_STRING = "SOME OTHER STRING"; - AttributeHolderServletContext servletContext = new AttributeHolderServletContext(); - public static final ImmutableSet<SessionTrackingMode> SOME_SET_OF_SESSION_TRACKING_MODE = ImmutableSet.of(); - - @Test(expected = UnsupportedOperationException.class) - public void getContextPath_is_not_supported() { - servletContext.getContextPath(); - } - - @Test(expected = UnsupportedOperationException.class) - public void getContext_is_not_supported() { - servletContext.getContext(SOME_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void getMajorVersion_is_not_supported() { - servletContext.getMajorVersion(); - } - - @Test(expected = UnsupportedOperationException.class) - public void getMinorVersion_is_not_supported() { - servletContext.getMinorVersion(); - } - - @Test(expected = UnsupportedOperationException.class) - public void getEffectiveMajorVersion_is_not_supported() { - servletContext.getEffectiveMajorVersion(); - } - - @Test(expected = UnsupportedOperationException.class) - public void getEffectiveMinorVersion_is_not_supported() { - servletContext.getEffectiveMinorVersion(); - } - - @Test(expected = UnsupportedOperationException.class) - public void getMimeType_is_not_supported() { - servletContext.getMimeType(SOME_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void getResourcePaths_is_not_supported() { - servletContext.getResourcePaths(SOME_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void getResource_is_not_supported() throws Exception { - servletContext.getResource(SOME_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void getResourceAsStream_is_not_supported() { - servletContext.getResourceAsStream(SOME_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void getRequestDispatcher_is_not_supported() { - servletContext.getRequestDispatcher(SOME_STRING); - - } - - @Test(expected = UnsupportedOperationException.class) - public void getNamedDispatcher_is_not_supported() { - servletContext.getNamedDispatcher(SOME_STRING); - - } - - @Test(expected = UnsupportedOperationException.class) - public void getServlet_is_not_supported() throws ServletException { - servletContext.getServlet(SOME_STRING); - - } - - @Test(expected = UnsupportedOperationException.class) - public void getServlets_is_not_supported() { - servletContext.getServlets(); - - } - - @Test(expected = UnsupportedOperationException.class) - public void getServletNames_is_not_supported() { - servletContext.getServletNames(); - - } - - @Test(expected = UnsupportedOperationException.class) - public void log_is_not_supported() { - servletContext.log(SOME_STRING); - - } - - @Test(expected = UnsupportedOperationException.class) - public void log1_is_not_supported() { - servletContext.log(SOME_EXCEPTION, SOME_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void log2_is_not_supported() { - servletContext.log(SOME_STRING, SOME_EXCEPTION); - } - - @Test(expected = UnsupportedOperationException.class) - public void getRealPath_is_not_supported() { - servletContext.getRealPath(SOME_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void getServerInfo_is_not_supported() { - servletContext.getServerInfo(); - } - - @Test(expected = UnsupportedOperationException.class) - public void getInitParameter_is_not_supported() { - servletContext.getInitParameter(SOME_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void getInitParameterNames_is_not_supported() { - servletContext.getInitParameterNames(); - } - - @Test(expected = UnsupportedOperationException.class) - public void setInitParameter_is_not_supported() { - servletContext.setInitParameter(SOME_STRING, SOME_STRING); - - } - - @Test - public void getAttribute_returns_null_when_attributes_are_empty() { - assertThat(servletContext.getAttribute(SOME_STRING)).isNull(); - } - - @Test - public void getAttribute_returns_attribute() { - servletContext.setAttribute(SOME_STRING, SOME_OTHER_STRING); - - assertThat(servletContext.getAttribute(SOME_STRING)).isEqualTo(SOME_OTHER_STRING); - } - - @Test - public void getAttributeNames_returns_empty_enumeration_if_attributes_are_empty() { - Enumeration<String> attributeNames = servletContext.getAttributeNames(); - assertThat(attributeNames.hasMoreElements()).isFalse(); - } - - @Test - public void getAttributeNames_returns_names_of_attributes() { - servletContext.setAttribute(SOME_STRING, new Object()); - servletContext.setAttribute(SOME_OTHER_STRING, new Object()); - - assertThat(Collections.list(servletContext.getAttributeNames())).containsOnly(SOME_STRING, SOME_OTHER_STRING); - } - - @Test - public void removeAttribute_removes_specified_attribute() { - servletContext.setAttribute(SOME_STRING, new Object()); - servletContext.setAttribute(SOME_OTHER_STRING, new Object()); - - servletContext.removeAttribute(SOME_OTHER_STRING); - - assertThat(servletContext.getAttribute(SOME_OTHER_STRING)).isNull(); - } - - @Test(expected = UnsupportedOperationException.class) - public void getServletContextName_is_not_supported() { - servletContext.getServletContextName(); - - } - - @Test(expected = UnsupportedOperationException.class) - public void addServlet_by_class_is_not_supported() { - servletContext.addServlet(SOME_STRING, Servlet.class); - } - - @Test(expected = UnsupportedOperationException.class) - public void addServlet_by_instance_is_not_supported() { - servletContext.addServlet(SOME_STRING, new Servlet() { - @Override - public void init(ServletConfig servletConfig) { - - } - - @Override - public ServletConfig getServletConfig() { - return null; - } - - @Override - public void service(ServletRequest servletRequest, ServletResponse servletResponse) { - - } - - @Override - public String getServletInfo() { - return null; - } - - @Override - public void destroy() { - - } - }); - - } - - @Test(expected = UnsupportedOperationException.class) - public void addServlet_by_string_is_not_supported() { - servletContext.addServlet(SOME_STRING, SOME_OTHER_STRING); - - } - - @Test(expected = UnsupportedOperationException.class) - public void createServlet_is_not_supported() throws ServletException { - servletContext.createServlet(Servlet.class); - } - - @Test(expected = UnsupportedOperationException.class) - public void getServletRegistration_is_not_supported() { - servletContext.getServletRegistration(SOME_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void getServletRegistrations_is_not_supported() { - servletContext.getServletRegistrations(); - } - - @Test(expected = UnsupportedOperationException.class) - public void addFilter_by_class_is_not_supported() { - servletContext.addFilter(SOME_STRING, Filter.class); - } - - @Test(expected = UnsupportedOperationException.class) - public void addFilter_by_instance_is_not_supported() { - servletContext.addFilter(SOME_STRING, new Filter() { - @Override - public void init(FilterConfig filterConfig) { - - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) { - - } - - @Override - public void destroy() { - - } - }); - } - - @Test(expected = UnsupportedOperationException.class) - public void addFilter2_by_string_is_not_supported() { - servletContext.addFilter(SOME_STRING, SOME_OTHER_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void createFilter_is_not_supported() throws ServletException { - servletContext.createFilter(Filter.class); - } - - @Test(expected = UnsupportedOperationException.class) - public void getFilterRegistration_is_not_supported() { - servletContext.getFilterRegistration(SOME_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void getFilterRegistrations_is_not_supported() { - servletContext.getFilterRegistrations(); - } - - @Test(expected = UnsupportedOperationException.class) - public void getSessionCookieConfig_is_not_supported() { - servletContext.getSessionCookieConfig(); - } - - @Test(expected = UnsupportedOperationException.class) - public void setSessionTrackingModes_is_not_supported() { - servletContext.setSessionTrackingModes(SOME_SET_OF_SESSION_TRACKING_MODE); - } - - @Test(expected = UnsupportedOperationException.class) - public void getDefaultSessionTrackingModes_is_not_supported() { - servletContext.getDefaultSessionTrackingModes(); - } - - @Test(expected = UnsupportedOperationException.class) - public void getEffectiveSessionTrackingModes_is_not_supported() { - servletContext.getEffectiveSessionTrackingModes(); - } - - @Test(expected = UnsupportedOperationException.class) - public void addListener_by_class_is_not_supported() { - servletContext.addListener(EventListener.class); - } - - @Test(expected = UnsupportedOperationException.class) - public void addListener_by_string_is_not_supported() { - servletContext.addListener(SOME_STRING); - } - - @Test(expected = UnsupportedOperationException.class) - public void addListener_by_instance_is_not_supported() { - servletContext.addListener(new EventListener() { - }); - } - - @Test(expected = UnsupportedOperationException.class) - public void createListener_is_not_supported() throws ServletException { - servletContext.createListener(EventListener.class); - } - - @Test(expected = UnsupportedOperationException.class) - public void getJspConfigDescriptor_is_not_supported() { - servletContext.getJspConfigDescriptor(); - } - - @Test(expected = UnsupportedOperationException.class) - public void getClassLoader_is_not_supported() { - servletContext.getClassLoader(); - } - - @Test(expected = UnsupportedOperationException.class) - public void declareRoles_is_not_supported() { - servletContext.declareRoles(); - } - - @Test(expected = UnsupportedOperationException.class) - public void getVirtualServerName_is_not_supported() { - servletContext.getVirtualServerName(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/MockUserSession.java b/server/sonar-server/src/test/java/org/sonar/server/tester/MockUserSession.java deleted file mode 100644 index e59b0eda6f5..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/MockUserSession.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.tester; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.user.AbstractUserSession; - -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Arrays.asList; -import static java.util.Objects.requireNonNull; -import static org.sonar.server.user.UserSession.IdentityProvider.SONARQUBE; - -public class MockUserSession extends AbstractMockUserSession<MockUserSession> { - private final String login; - private String uuid; - private boolean root = false; - private Integer userId; - private String name; - private List<GroupDto> groups = new ArrayList<>(); - private IdentityProvider identityProvider; - private ExternalIdentity externalIdentity; - - public MockUserSession(String login) { - super(MockUserSession.class); - checkArgument(!login.isEmpty()); - this.login = login; - setUuid(login + "uuid"); - setUserId(login.hashCode()); - setName(login + " name"); - setInternalIdentity(); - } - - public MockUserSession(UserDto userDto) { - super(MockUserSession.class); - checkArgument(!userDto.getLogin().isEmpty()); - this.login = userDto.getLogin(); - setUuid(userDto.getUuid()); - setUserId(userDto.getId()); - setName(userDto.getName()); - AbstractUserSession.Identity identity = computeIdentity(userDto); - this.identityProvider = identity.getIdentityProvider(); - this.externalIdentity = identity.getExternalIdentity(); - } - - @Override - public boolean isLoggedIn() { - return true; - } - - @Override - public boolean isRoot() { - return root; - } - - public void setRoot(boolean root) { - this.root = root; - } - - @Override - public String getLogin() { - return this.login; - } - - @Override - public String getUuid() { - return this.uuid; - } - - public MockUserSession setUuid(String uuid) { - this.uuid = requireNonNull(uuid); - return this; - } - - @Override - public String getName() { - return this.name; - } - - public MockUserSession setName(String s) { - this.name = requireNonNull(s); - return this; - } - - @Override - public Integer getUserId() { - return this.userId; - } - - public MockUserSession setUserId(int userId) { - this.userId = userId; - return this; - } - - @Override - public Collection<GroupDto> getGroups() { - return groups; - } - - public MockUserSession setGroups(GroupDto... groups) { - this.groups = asList(groups); - return this; - } - - @Override - public Optional<IdentityProvider> getIdentityProvider() { - return Optional.ofNullable(identityProvider); - } - - public void setExternalIdentity(IdentityProvider identityProvider, ExternalIdentity externalIdentity) { - checkArgument(identityProvider != SONARQUBE); - this.identityProvider = identityProvider; - this.externalIdentity = requireNonNull(externalIdentity); - } - - public void setInternalIdentity() { - this.identityProvider = SONARQUBE; - this.externalIdentity = null; - } - - @Override - public Optional<ExternalIdentity> getExternalIdentity() { - return Optional.ofNullable(externalIdentity); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/MockUserSessionTest.java b/server/sonar-server/src/test/java/org/sonar/server/tester/MockUserSessionTest.java deleted file mode 100644 index a57efa676d0..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/MockUserSessionTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.tester; - -import org.junit.Test; -import org.sonar.db.user.GroupDto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.db.user.GroupTesting.newGroupDto; - -public class MockUserSessionTest { - @Test - public void set_mock_session() { - GroupDto group = newGroupDto(); - MockUserSession mock = new MockUserSession("foo").setGroups(group); - - assertThat(mock.getLogin()).isEqualTo("foo"); - assertThat(mock.getUuid()).isEqualTo("foouuid"); - assertThat(mock.getGroups()).extracting(GroupDto::getId).containsOnly(group.getId()); - assertThat(mock.isLoggedIn()).isTrue(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/UserSessionRule.java b/server/sonar-server/src/test/java/org/sonar/server/tester/UserSessionRule.java deleted file mode 100644 index 5e7cc4147d5..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/UserSessionRule.java +++ /dev/null @@ -1,373 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.tester; - -import com.google.common.base.Preconditions; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.user.UserSession; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - -/** - * {@code UserSessionRule} is intended to be used as a {@link org.junit.Rule} to easily manage {@link UserSession} in - * unit tests. - * <p> - * It can be used as a {@link org.junit.ClassRule} but be careful not to modify its states from inside tests methods - * unless you purposely want to have side effects between each tests. - * </p> - * <p> - * One can define user session behavior which should apply on all tests directly on the property, eg.: - * <pre> - * {@literal @}Rule - * public UserSessionRule userSession = UserSessionRule.standalone().login("admin").setOrganizationPermissions(OrganizationPermissions.SYSTEM_ADMIN); - * </pre> - * </p> - * <p> - * Behavior defined at property-level can obviously be override at test method level. For example, one could define - * all methods to use an authenticated session such as presented above but can easily overwrite that behavior in a - * specific test method as follow: - * <pre> - * {@literal @}Test - * public void test_method() { - * userSession.standalone(); - * {@literal [...]} - * } - * </pre> - * </p> - * <p> - * {@code UserSessionRule}, emulates by default an anonymous - * session. Therefore, call {@code UserSessionRule.standalone()} is equivalent to calling - * {@code UserSessionRule.standalone().anonymous()}. - * </p> - * <p> - * To emulate an identified user, either use method {@link #logIn(String)} if you want to specify the user's login, or - * method {@link #logIn()} which will do the same but using the value of {@link #DEFAULT_LOGIN} as the user's login - * (use the latest override if you don't care about the actual value of the login, it will save noise in your test). - * </p> - */ -public class UserSessionRule implements TestRule, UserSession { - private static final String DEFAULT_LOGIN = "default_login"; - - private UserSession currentUserSession; - - private UserSessionRule() { - anonymous(); - } - - public static UserSessionRule standalone() { - return new UserSessionRule(); - } - - /** - * Log in with the default login {@link #DEFAULT_LOGIN} - */ - public UserSessionRule logIn() { - return logIn(DEFAULT_LOGIN); - } - - /** - * Log in with the specified login - */ - public UserSessionRule logIn(String login) { - setCurrentUserSession(new MockUserSession(login)); - return this; - } - - /** - * Log in with the specified login - */ - public UserSessionRule logIn(UserDto userDto) { - setCurrentUserSession(new MockUserSession(userDto)); - return this; - } - - /** - * Disconnect/go anonymous - */ - public UserSessionRule anonymous() { - setCurrentUserSession(new AnonymousMockUserSession()); - return this; - } - - public UserSessionRule setRoot() { - ensureMockUserSession().setRoot(true); - return this; - } - - public UserSessionRule setNonRoot() { - ensureMockUserSession().setRoot(false); - return this; - } - - public UserSessionRule setSystemAdministrator() { - ensureMockUserSession().setSystemAdministrator(true); - return this; - } - - public UserSessionRule setNonSystemAdministrator() { - ensureMockUserSession().setSystemAdministrator(false); - return this; - } - - public UserSessionRule setExternalIdentity(IdentityProvider identityProvider, ExternalIdentity externalIdentity) { - ensureMockUserSession().setExternalIdentity(identityProvider, externalIdentity); - return this; - } - - public UserSessionRule setInternalIdentity() { - ensureMockUserSession().setInternalIdentity(); - return this; - } - - @Override - public Statement apply(Statement statement, Description description) { - return this.statement(statement); - } - - private Statement statement(final Statement base) { - return new Statement() { - public void evaluate() throws Throwable { - UserSessionRule.this.before(); - - try { - base.evaluate(); - } finally { - UserSessionRule.this.after(); - } - - } - }; - } - - protected void before() { - setCurrentUserSession(currentUserSession); - } - - protected void after() { - this.currentUserSession = null; - } - - public void set(UserSession userSession) { - checkNotNull(userSession); - setCurrentUserSession(userSession); - } - - public UserSessionRule registerComponents(ComponentDto... componentDtos) { - ensureAbstractMockUserSession().registerComponents(componentDtos); - return this; - } - - public UserSessionRule addProjectPermission(String projectPermission, ComponentDto... components) { - ensureAbstractMockUserSession().addProjectPermission(projectPermission, components); - return this; - } - - public UserSessionRule addPermission(OrganizationPermission permission, String organizationUuid) { - ensureAbstractMockUserSession().addPermission(permission, organizationUuid); - return this; - } - - public UserSessionRule addPermission(OrganizationPermission permission, OrganizationDto organization) { - ensureAbstractMockUserSession().addPermission(permission, organization.getUuid()); - return this; - } - - public UserSessionRule setUserId(@Nullable Integer userId) { - ensureMockUserSession().setUserId(userId); - return this; - } - - /** - * Groups that user is member of. User must be logged in. An exception - * is thrown if session is anonymous. - */ - public UserSessionRule setGroups(GroupDto... groups) { - ensureMockUserSession().setGroups(groups); - return this; - } - - public UserSessionRule setName(@Nullable String s) { - ensureMockUserSession().setName(s); - return this; - } - - private AbstractMockUserSession ensureAbstractMockUserSession() { - checkState(currentUserSession instanceof AbstractMockUserSession, "rule state can not be changed if a UserSession has explicitly been provided"); - return (AbstractMockUserSession) currentUserSession; - } - - private MockUserSession ensureMockUserSession() { - checkState(currentUserSession instanceof MockUserSession, "rule state can not be changed if a UserSession has explicitly been provided"); - return (MockUserSession) currentUserSession; - } - - private void setCurrentUserSession(UserSession userSession) { - this.currentUserSession = Preconditions.checkNotNull(userSession); - } - - @Override - public boolean hasComponentPermission(String permission, ComponentDto component) { - return currentUserSession.hasComponentPermission(permission, component); - } - - @Override - public boolean hasComponentUuidPermission(String permission, String componentUuid) { - return currentUserSession.hasComponentUuidPermission(permission, componentUuid); - } - - @Override - public List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components) { - return currentUserSession.keepAuthorizedComponents(permission, components); - } - - @Override - @CheckForNull - public String getLogin() { - return currentUserSession.getLogin(); - } - - @Override - @CheckForNull - public String getUuid() { - return currentUserSession.getUuid(); - } - - @Override - @CheckForNull - public String getName() { - return currentUserSession.getName(); - } - - @Override - @CheckForNull - public Integer getUserId() { - return currentUserSession.getUserId(); - } - - @Override - public Collection<GroupDto> getGroups() { - return currentUserSession.getGroups(); - } - - @Override - public Optional<IdentityProvider> getIdentityProvider() { - return currentUserSession.getIdentityProvider(); - } - - @Override - public Optional<ExternalIdentity> getExternalIdentity() { - return currentUserSession.getExternalIdentity(); - } - - @Override - public boolean isLoggedIn() { - return currentUserSession.isLoggedIn(); - } - - @Override - public boolean isRoot() { - return currentUserSession.isRoot(); - } - - @Override - public UserSession checkIsRoot() { - return currentUserSession.checkIsRoot(); - } - - @Override - public UserSession checkLoggedIn() { - currentUserSession.checkLoggedIn(); - return this; - } - - @Override - public boolean hasPermission(OrganizationPermission permission, OrganizationDto organization) { - return currentUserSession.hasPermission(permission, organization); - } - - @Override - public boolean hasPermission(OrganizationPermission permission, String organizationUuid) { - return currentUserSession.hasPermission(permission, organizationUuid); - } - - @Override - public UserSession checkPermission(OrganizationPermission permission, OrganizationDto organization) { - currentUserSession.checkPermission(permission, organization); - return this; - } - - @Override - public UserSession checkPermission(OrganizationPermission permission, String organizationUuid) { - currentUserSession.checkPermission(permission, organizationUuid); - return this; - } - - @Override - public UserSession checkComponentPermission(String projectPermission, ComponentDto component) { - currentUserSession.checkComponentPermission(projectPermission, component); - return this; - } - - @Override - public UserSession checkComponentUuidPermission(String permission, String componentUuid) { - currentUserSession.checkComponentUuidPermission(permission, componentUuid); - return this; - } - - @Override - public boolean isSystemAdministrator() { - return currentUserSession.isSystemAdministrator(); - } - - @Override - public UserSession checkIsSystemAdministrator() { - currentUserSession.checkIsSystemAdministrator(); - return this; - } - - @Override - public boolean hasMembership(OrganizationDto organization) { - return currentUserSession.hasMembership(organization); - } - - @Override - public UserSession checkMembership(OrganizationDto organization) { - currentUserSession.checkMembership(organization); - return this; - } - - public UserSessionRule addMembership(OrganizationDto organization) { - ensureAbstractMockUserSession().addOrganizationMembership(organization); - return this; - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/package-info.java b/server/sonar-server/src/test/java/org/sonar/server/tester/package-info.java deleted file mode 100644 index 27beb989b45..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.tester; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/CompatibilityRealmTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/CompatibilityRealmTest.java deleted file mode 100644 index be3f0360ddb..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/CompatibilityRealmTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.junit.Test; -import org.sonar.api.security.LoginPasswordAuthenticator; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class CompatibilityRealmTest { - - @Test - public void shouldDelegate() { - LoginPasswordAuthenticator authenticator = mock(LoginPasswordAuthenticator.class); - CompatibilityRealm realm = new CompatibilityRealm(authenticator); - realm.init(); - verify(authenticator).init(); - assertThat(realm.getLoginPasswordAuthenticator()).isSameAs(authenticator); - assertThat(realm.getName()).isEqualTo("CompatibilityRealm[" + authenticator.getClass().getName() + "]"); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/DefaultUserTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/DefaultUserTest.java deleted file mode 100644 index 5450d38987a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/DefaultUserTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.junit.Test; -import org.sonar.core.user.DefaultUser; - -import static org.assertj.core.api.Assertions.assertThat; - -public class DefaultUserTest { - @Test - public void test_object_methods() { - DefaultUser john = new DefaultUser().setLogin("john").setName("John"); - DefaultUser eric = new DefaultUser().setLogin("eric").setName("Eric"); - - assertThat(john).isEqualTo(john); - assertThat(john).isNotEqualTo(eric); - assertThat(john.hashCode()).isEqualTo(john.hashCode()); - assertThat(john.toString()).contains("login=john").contains("name=John"); - } - - @Test - public void test_email() { - DefaultUser user = new DefaultUser(); - assertThat(user.email()).isNull(); - - user.setEmail(""); - assertThat(user.email()).isNull(); - - user.setEmail(" "); - assertThat(user.email()).isNull(); - - user.setEmail("s@b.com"); - assertThat(user.email()).isEqualTo("s@b.com"); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/DoPrivilegedTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/DoPrivilegedTest.java deleted file mode 100644 index cd9f2af3a5a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/DoPrivilegedTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.junit.Before; -import org.junit.Test; -import org.sonar.db.component.ComponentDto; -import org.sonar.server.tester.MockUserSession; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto; - -public class DoPrivilegedTest { - - private static final String LOGIN = "dalailaHidou!"; - - private ThreadLocalUserSession threadLocalUserSession = new ThreadLocalUserSession(); - private MockUserSession session = new MockUserSession(LOGIN); - - @Before - public void setUp() { - threadLocalUserSession.set(session); - } - - @Test - public void allow_everything_in_privileged_block_only() { - UserSessionCatcherTask catcher = new UserSessionCatcherTask(); - - DoPrivileged.execute(catcher); - - // verify the session used inside Privileged task - assertThat(catcher.userSession.isLoggedIn()).isFalse(); - assertThat(catcher.userSession.hasComponentPermission("any permission", new ComponentDto())).isTrue(); - assertThat(catcher.userSession.isSystemAdministrator()).isTrue(); - assertThat(catcher.userSession.hasMembership(newOrganizationDto())).isTrue(); - - // verify session in place after task is done - assertThat(threadLocalUserSession.get()).isSameAs(session); - } - - @Test - public void loose_privileges_on_exception() { - UserSessionCatcherTask catcher = new UserSessionCatcherTask() { - @Override - protected void doPrivileged() { - super.doPrivileged(); - throw new RuntimeException("Test to lose privileges"); - } - }; - - try { - DoPrivileged.execute(catcher); - fail("An exception should have been raised!"); - } catch (Throwable ignored) { - // verify session in place after task is done - assertThat(threadLocalUserSession.get()).isSameAs(session); - - // verify the session used inside Privileged task - assertThat(catcher.userSession.isLoggedIn()).isFalse(); - assertThat(catcher.userSession.hasComponentPermission("any permission", new ComponentDto())).isTrue(); - assertThat(catcher.userSession.hasMembership(newOrganizationDto())).isTrue(); - } - } - - private class UserSessionCatcherTask extends DoPrivileged.Task { - UserSession userSession; - - public UserSessionCatcherTask() { - super(DoPrivilegedTest.this.threadLocalUserSession); - } - - @Override - protected void doPrivileged() { - userSession = threadLocalUserSession.get(); - } - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ExternalIdentityTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ExternalIdentityTest.java deleted file mode 100644 index b6857add6eb..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ExternalIdentityTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ExternalIdentityTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void create_external_identity() { - ExternalIdentity externalIdentity = new ExternalIdentity("github", "login", "ABCD"); - assertThat(externalIdentity.getLogin()).isEqualTo("login"); - assertThat(externalIdentity.getProvider()).isEqualTo("github"); - assertThat(externalIdentity.getId()).isEqualTo("ABCD"); - } - - @Test - public void login_is_used_when_id_is_not_provided() { - ExternalIdentity externalIdentity = new ExternalIdentity("github", "login", null); - assertThat(externalIdentity.getLogin()).isEqualTo("login"); - assertThat(externalIdentity.getProvider()).isEqualTo("github"); - assertThat(externalIdentity.getId()).isEqualTo("login"); - } - - @Test - public void fail_with_NPE_when_identity_provider_is_null() { - thrown.expect(NullPointerException.class); - thrown.expectMessage("Identity provider cannot be null"); - - new ExternalIdentity(null, "login", "ABCD"); - } - - @Test - public void fail_with_NPE_when_identity_login_is_null() { - thrown.expect(NullPointerException.class); - thrown.expectMessage("Identity login cannot be null"); - - new ExternalIdentity("github", null, "ABCD"); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/NewUserNotifierTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/NewUserNotifierTest.java deleted file mode 100644 index 441487aa7ab..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/NewUserNotifierTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.junit.Test; -import org.sonar.api.platform.NewUserHandler; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class NewUserNotifierTest { - - NewUserHandler.Context context = NewUserHandler.Context.builder().setLogin("marius").setName("Marius").build(); - - @Test - public void do_not_fail_if_no_handlers() { - NewUserNotifier notifier = new NewUserNotifier(); - - notifier.onNewUser(context); - } - - @Test - public void execute_handlers_on_new_user() { - NewUserHandler handler1 = mock(NewUserHandler.class); - NewUserHandler handler2 = mock(NewUserHandler.class); - NewUserNotifier notifier = new NewUserNotifier(new NewUserHandler[]{handler1, handler2}); - - - notifier.onNewUser(context); - - verify(handler1).doOnNewUser(context); - verify(handler2).doOnNewUser(context); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/NewUserTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/NewUserTest.java deleted file mode 100644 index 9d412b7a519..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/NewUserTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; - -public class NewUserTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Test - public void create_new_user() { - NewUser newUser = NewUser.builder() - .setLogin("login") - .setName("name") - .setEmail("email") - .setPassword("password") - .setScmAccounts(asList("login1", "login2")) - .build(); - - assertThat(newUser.login()).isEqualTo("login"); - assertThat(newUser.name()).isEqualTo("name"); - assertThat(newUser.email()).isEqualTo("email"); - assertThat(newUser.password()).isEqualTo("password"); - assertThat(newUser.scmAccounts()).contains("login1", "login2"); - assertThat(newUser.externalIdentity()).isNull(); - } - - @Test - public void create_new_user_with_minimal_fields() { - NewUser newUser = NewUser.builder().build(); - - assertThat(newUser.login()).isNull(); - assertThat(newUser.name()).isNull(); - assertThat(newUser.email()).isNull(); - assertThat(newUser.password()).isNull(); - assertThat(newUser.scmAccounts()).isEmpty(); - } - - @Test - public void create_new_user_with_authority() { - NewUser newUser = NewUser.builder() - .setLogin("login") - .setName("name") - .setEmail("email") - .setExternalIdentity(new ExternalIdentity("github", "github_login", "ABCD")) - .build(); - - assertThat(newUser.externalIdentity().getProvider()).isEqualTo("github"); - assertThat(newUser.externalIdentity().getLogin()).isEqualTo("github_login"); - assertThat(newUser.externalIdentity().getId()).isEqualTo("ABCD"); - } - - @Test - public void fail_to_set_password_when_external_identity_is_set() { - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Password should not be set with an external identity"); - - NewUser.builder() - .setLogin("login") - .setName("name") - .setEmail("email") - .setPassword("password") - .setExternalIdentity(new ExternalIdentity("github", "github_login", "ABCD")) - .build(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/SecurityRealmFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/SecurityRealmFactoryTest.java deleted file mode 100644 index d680692abd6..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/SecurityRealmFactoryTest.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.junit.Test; -import org.sonar.api.CoreProperties; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.security.LoginPasswordAuthenticator; -import org.sonar.api.security.SecurityRealm; -import org.sonar.api.utils.SonarException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -public class SecurityRealmFactoryTest { - - private MapSettings settings = new MapSettings(); - - /** - * Typical usage. - */ - @Test - public void should_select_realm_and_start() { - SecurityRealm realm = spy(new FakeRealm()); - settings.setProperty("sonar.security.realm", realm.getName()); - - SecurityRealmFactory factory = new SecurityRealmFactory(settings.asConfig(), new SecurityRealm[] {realm}); - factory.start(); - assertThat(factory.getRealm()).isSameAs(realm); - assertThat(factory.hasExternalAuthentication()).isTrue(); - verify(realm).init(); - - factory.stop(); - } - - @Test - public void do_not_fail_if_no_realms() { - SecurityRealmFactory factory = new SecurityRealmFactory(settings.asConfig()); - factory.start(); - assertThat(factory.getRealm()).isNull(); - assertThat(factory.hasExternalAuthentication()).isFalse(); - } - - @Test - public void realm_not_found() { - settings.setProperty("sonar.security.realm", "Fake"); - - try { - new SecurityRealmFactory(settings.asConfig()); - fail(); - } catch (SonarException e) { - assertThat(e.getMessage()).contains("Realm 'Fake' not found."); - } - } - - @Test - public void should_provide_compatibility_for_authenticator() { - settings.setProperty(CoreProperties.CORE_AUTHENTICATOR_CLASS, FakeAuthenticator.class.getName()); - LoginPasswordAuthenticator authenticator = new FakeAuthenticator(); - - SecurityRealmFactory factory = new SecurityRealmFactory(settings.asConfig(), new LoginPasswordAuthenticator[] {authenticator}); - SecurityRealm realm = factory.getRealm(); - assertThat(realm).isInstanceOf(CompatibilityRealm.class); - } - - @Test - public void should_take_precedence_over_authenticator() { - SecurityRealm realm = new FakeRealm(); - settings.setProperty("sonar.security.realm", realm.getName()); - LoginPasswordAuthenticator authenticator = new FakeAuthenticator(); - settings.setProperty(CoreProperties.CORE_AUTHENTICATOR_CLASS, FakeAuthenticator.class.getName()); - - SecurityRealmFactory factory = new SecurityRealmFactory(settings.asConfig(), new SecurityRealm[] {realm}, - new LoginPasswordAuthenticator[] {authenticator}); - assertThat(factory.getRealm()).isSameAs(realm); - } - - @Test - public void authenticator_not_found() { - settings.setProperty(CoreProperties.CORE_AUTHENTICATOR_CLASS, "Fake"); - - try { - new SecurityRealmFactory(settings.asConfig()); - fail(); - } catch (SonarException e) { - assertThat(e.getMessage()).contains("Authenticator 'Fake' not found."); - } - } - - @Test - public void ignore_startup_failure() { - SecurityRealm realm = spy(new AlwaysFailsRealm()); - settings.setProperty("sonar.security.realm", realm.getName()); - settings.setProperty(CoreProperties.CORE_AUTHENTICATOR_IGNORE_STARTUP_FAILURE, true); - - new SecurityRealmFactory(settings.asConfig(), new SecurityRealm[] {realm}).start(); - verify(realm).init(); - } - - @Test - public void should_fail() { - SecurityRealm realm = spy(new AlwaysFailsRealm()); - settings.setProperty("sonar.security.realm", realm.getName()); - - try { - new SecurityRealmFactory(settings.asConfig(), new SecurityRealm[] {realm}).start(); - fail(); - } catch (SonarException e) { - assertThat(e.getCause()).isInstanceOf(IllegalStateException.class); - assertThat(e.getMessage()).contains("Security realm fails to start"); - } - } - - private static class AlwaysFailsRealm extends FakeRealm { - @Override - public void init() { - throw new IllegalStateException(); - } - } - - private static class FakeRealm extends SecurityRealm { - @Override - public LoginPasswordAuthenticator getLoginPasswordAuthenticator() { - return null; - } - } - - private static class FakeAuthenticator implements LoginPasswordAuthenticator { - public void init() { - } - - public boolean authenticate(String login, String password) { - return false; - } - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java deleted file mode 100644 index af654c54493..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java +++ /dev/null @@ -1,710 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import java.util.Arrays; -import javax.annotation.Nullable; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.utils.System2; -import org.sonar.api.web.UserRole; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.exceptions.ForbiddenException; -import org.sonar.server.organization.TestDefaultOrganizationProvider; -import org.sonar.server.organization.TestOrganizationFlags; - -import static com.google.common.base.Preconditions.checkState; -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.core.permission.GlobalPermissions.PROVISIONING; -import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; -import static org.sonar.db.component.ComponentTesting.newChildComponent; -import static org.sonar.db.component.ComponentTesting.newFileDto; -import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; -import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS; -import static org.sonar.db.permission.OrganizationPermission.SCAN; - -public class ServerUserSessionTest { - - @Rule - public DbTester db = DbTester.create(System2.INSTANCE); - @Rule - public ExpectedException expectedException = ExpectedException.none(); - private DbClient dbClient = db.getDbClient(); - private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); - private TestDefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); - - @Test - public void anonymous_is_not_logged_in_and_does_not_have_login() { - UserSession session = newAnonymousSession(); - - assertThat(session.getLogin()).isNull(); - assertThat(session.getUuid()).isNull(); - assertThat(session.isLoggedIn()).isFalse(); - } - - @Test - public void getGroups_is_empty_on_anonymous() { - assertThat(newAnonymousSession().getGroups()).isEmpty(); - } - - @Test - public void getGroups_is_empty_if_user_is_not_member_of_any_group() { - UserDto user = db.users().insertUser(); - assertThat(newUserSession(user).getGroups()).isEmpty(); - } - - @Test - public void getGroups_returns_the_groups_of_logged_in_user() { - UserDto user = db.users().insertUser(); - GroupDto group1 = db.users().insertGroup(); - GroupDto group2 = db.users().insertGroup(); - db.users().insertMember(group1, user); - db.users().insertMember(group2, user); - - assertThat(newUserSession(user).getGroups()).extracting(GroupDto::getId).containsOnly(group1.getId(), group2.getId()); - } - - @Test - public void getGroups_keeps_groups_in_cache() { - UserDto user = db.users().insertUser(); - GroupDto group1 = db.users().insertGroup(); - GroupDto group2 = db.users().insertGroup(); - db.users().insertMember(group1, user); - - ServerUserSession session = newUserSession(user); - assertThat(session.getGroups()).extracting(GroupDto::getId).containsOnly(group1.getId()); - - // membership updated but not cache - db.users().insertMember(group2, user); - assertThat(session.getGroups()).extracting(GroupDto::getId).containsOnly(group1.getId()); - } - - @Test - public void isRoot_is_false_is_flag_root_is_false_on_UserDto() { - UserDto root = db.users().insertUser(); - root = db.users().makeRoot(root); - assertThat(newUserSession(root).isRoot()).isTrue(); - - UserDto notRoot = db.users().insertUser(); - assertThat(newUserSession(notRoot).isRoot()).isFalse(); - } - - @Test - public void checkIsRoot_throws_IPFE_if_flag_root_is_false_on_UserDto() { - UserDto user = db.users().insertUser(); - UserSession underTest = newUserSession(user); - - expectInsufficientPrivilegesForbiddenException(); - - underTest.checkIsRoot(); - } - - @Test - public void checkIsRoot_does_not_fail_if_flag_root_is_true_on_UserDto() { - UserDto root = db.users().insertUser(); - root = db.users().makeRoot(root); - - UserSession underTest = newUserSession(root); - - assertThat(underTest.checkIsRoot()).isSameAs(underTest); - } - - @Test - public void hasComponentUuidPermission_returns_true_when_flag_root_is_true_on_UserDto_no_matter_if_user_has_project_permission_for_given_uuid() { - UserDto root = db.users().insertUser(); - root = db.users().makeRoot(root); - OrganizationDto organization = db.organizations().insert(); - ComponentDto project = db.components().insertPrivateProject(organization); - ComponentDto file = db.components().insertComponent(newFileDto(project)); - - UserSession underTest = newUserSession(root); - - assertThat(underTest.hasComponentUuidPermission(UserRole.USER, file.uuid())).isTrue(); - assertThat(underTest.hasComponentUuidPermission(UserRole.CODEVIEWER, file.uuid())).isTrue(); - assertThat(underTest.hasComponentUuidPermission(UserRole.ADMIN, file.uuid())).isTrue(); - assertThat(underTest.hasComponentUuidPermission("whatever", "who cares?")).isTrue(); - } - - @Test - public void checkComponentUuidPermission_succeeds_if_user_has_permission_for_specified_uuid_in_db() { - UserDto root = db.users().insertUser(); - root = db.users().makeRoot(root); - OrganizationDto organization = db.organizations().insert(); - ComponentDto project = db.components().insertPrivateProject(organization); - ComponentDto file = db.components().insertComponent(newFileDto(project)); - - UserSession underTest = newUserSession(root); - - assertThat(underTest.checkComponentUuidPermission(UserRole.USER, file.uuid())).isSameAs(underTest); - assertThat(underTest.checkComponentUuidPermission("whatever", "who cares?")).isSameAs(underTest); - } - - @Test - public void checkComponentUuidPermission_fails_with_FE_when_user_has_not_permission_for_specified_uuid_in_db() { - UserDto user = db.users().insertUser(); - ComponentDto project = db.components().insertPrivateProject(); - db.users().insertProjectPermissionOnUser(user, UserRole.USER, project); - UserSession session = newUserSession(user); - - expectInsufficientPrivilegesForbiddenException(); - - session.checkComponentUuidPermission(UserRole.USER, "another-uuid"); - } - - @Test - public void checkPermission_throws_ForbiddenException_when_user_doesnt_have_the_specified_permission_on_organization() { - OrganizationDto org = db.organizations().insert(); - UserDto user = db.users().insertUser(); - - expectInsufficientPrivilegesForbiddenException(); - - newUserSession(user).checkPermission(PROVISION_PROJECTS, org); - } - - @Test - public void checkPermission_succeeds_when_user_has_the_specified_permission_on_organization() { - OrganizationDto org = db.organizations().insert(); - UserDto root = db.users().insertUser(); - root = db.users().makeRoot(root); - db.users().insertPermissionOnUser(org, root, PROVISIONING); - - newUserSession(root).checkPermission(PROVISION_PROJECTS, org); - } - - @Test - public void checkPermission_succeeds_when_user_is_root() { - OrganizationDto org = db.organizations().insert(); - UserDto root = db.users().insertUser(); - root = db.users().makeRoot(root); - - newUserSession(root).checkPermission(PROVISION_PROJECTS, org); - } - - @Test - public void test_hasPermission_on_organization_for_logged_in_user() { - OrganizationDto org = db.organizations().insert(); - ComponentDto project = db.components().insertPrivateProject(org); - UserDto user = db.users().insertUser(); - db.users().insertPermissionOnUser(org, user, PROVISION_PROJECTS); - db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, project); - - UserSession session = newUserSession(user); - assertThat(session.hasPermission(PROVISION_PROJECTS, org.getUuid())).isTrue(); - assertThat(session.hasPermission(ADMINISTER, org.getUuid())).isFalse(); - assertThat(session.hasPermission(PROVISION_PROJECTS, "another-org")).isFalse(); - } - - @Test - public void test_hasPermission_on_organization_for_anonymous_user() { - OrganizationDto org = db.organizations().insert(); - db.users().insertPermissionOnAnyone(org, PROVISION_PROJECTS); - - UserSession session = newAnonymousSession(); - assertThat(session.hasPermission(PROVISION_PROJECTS, org.getUuid())).isTrue(); - assertThat(session.hasPermission(ADMINISTER, org.getUuid())).isFalse(); - assertThat(session.hasPermission(PROVISION_PROJECTS, "another-org")).isFalse(); - } - - @Test - public void hasPermission_on_organization_keeps_cache_of_permissions_of_logged_in_user() { - OrganizationDto org = db.organizations().insert(); - UserDto user = db.users().insertUser(); - db.users().insertPermissionOnUser(org, user, PROVISIONING); - - UserSession session = newUserSession(user); - - // feed the cache - assertThat(session.hasPermission(PROVISION_PROJECTS, org.getUuid())).isTrue(); - - // change permissions without updating the cache - db.users().deletePermissionFromUser(org, user, PROVISION_PROJECTS); - db.users().insertPermissionOnUser(org, user, SCAN); - assertThat(session.hasPermission(PROVISION_PROJECTS, org.getUuid())).isTrue(); - assertThat(session.hasPermission(ADMINISTER, org.getUuid())).isFalse(); - assertThat(session.hasPermission(SCAN, org.getUuid())).isFalse(); - } - - @Test - public void hasPermission_on_organization_keeps_cache_of_permissions_of_anonymous_user() { - OrganizationDto org = db.organizations().insert(); - db.users().insertPermissionOnAnyone(org, PROVISION_PROJECTS); - - UserSession session = newAnonymousSession(); - - // feed the cache - assertThat(session.hasPermission(PROVISION_PROJECTS, org.getUuid())).isTrue(); - - // change permissions without updating the cache - db.users().insertPermissionOnAnyone(org, SCAN); - assertThat(session.hasPermission(PROVISION_PROJECTS, org.getUuid())).isTrue(); - assertThat(session.hasPermission(SCAN, org.getUuid())).isFalse(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_true_for_anonymous_user_for_permissions_USER_and_CODEVIEWER_on_public_projects_without_permissions() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - - ServerUserSession underTest = newAnonymousSession(); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, publicProject)).isTrue(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_true_for_anonymous_user_for_permissions_USER_and_CODEVIEWER_on_public_projects_with_global_permissions() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - db.users().insertProjectPermissionOnAnyone("p1", publicProject); - - ServerUserSession underTest = newAnonymousSession(); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, publicProject)).isTrue(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_true_for_anonymous_user_for_permissions_USER_and_CODEVIEWER_on_public_projects_with_group_permissions() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - db.users().insertProjectPermissionOnGroup(db.users().insertGroup(organization), "p1", publicProject); - - ServerUserSession underTest = newAnonymousSession(); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, publicProject)).isTrue(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_true_for_anonymous_user_for_permissions_USER_and_CODEVIEWER_on_public_projects_with_user_permissions() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - db.users().insertProjectPermissionOnUser(db.users().insertUser(), "p1", publicProject); - - ServerUserSession underTest = newAnonymousSession(); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, publicProject)).isTrue(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_false_for_authenticated_user_for_permissions_USER_and_CODEVIEWER_on_private_projects_without_permissions() { - UserDto user = db.users().insertUser(); - ComponentDto privateProject = db.components().insertPrivateProject(); - - ServerUserSession underTest = newUserSession(user); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, privateProject)).isFalse(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, privateProject)).isFalse(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_false_for_authenticated_user_for_permissions_USER_and_CODEVIEWER_on_private_projects_with_group_permissions() { - UserDto user = db.users().insertUser(); - OrganizationDto organization = db.organizations().insert(); - ComponentDto privateProject = db.components().insertPrivateProject(organization); - db.users().insertProjectPermissionOnGroup(db.users().insertGroup(organization), "p1", privateProject); - - ServerUserSession underTest = newUserSession(user); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, privateProject)).isFalse(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, privateProject)).isFalse(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_false_for_authenticated_user_for_permissions_USER_and_CODEVIEWER_on_private_projects_with_user_permissions() { - UserDto user = db.users().insertUser(); - ComponentDto privateProject = db.components().insertPrivateProject(); - db.users().insertProjectPermissionOnUser(db.users().insertUser(), "p1", privateProject); - - ServerUserSession underTest = newUserSession(user); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, privateProject)).isFalse(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, privateProject)).isFalse(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_true_for_anonymous_user_for_inserted_permissions_on_group_AnyOne_on_public_projects() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - db.users().insertProjectPermissionOnAnyone("p1", publicProject); - - ServerUserSession underTest = newAnonymousSession(); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", publicProject)).isTrue(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_false_for_anonymous_user_for_inserted_permissions_on_group_on_public_projects() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - GroupDto group = db.users().insertGroup(organization); - db.users().insertProjectPermissionOnGroup(group, "p1", publicProject); - - ServerUserSession underTest = newAnonymousSession(); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", publicProject)).isFalse(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_false_for_anonymous_user_for_inserted_permissions_on_group_on_private_projects() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto privateProject = db.components().insertPrivateProject(organization); - GroupDto group = db.users().insertGroup(organization); - db.users().insertProjectPermissionOnGroup(group, "p1", privateProject); - - ServerUserSession underTest = newAnonymousSession(); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", privateProject)).isFalse(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_false_for_anonymous_user_for_inserted_permissions_on_user_on_public_projects() { - UserDto user = db.users().insertUser(); - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - db.users().insertProjectPermissionOnUser(user, "p1", publicProject); - - ServerUserSession underTest = newAnonymousSession(); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", publicProject)).isFalse(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_false_for_anonymous_user_for_inserted_permissions_on_user_on_private_projects() { - UserDto user = db.users().insertUser(); - ComponentDto project = db.components().insertPrivateProject(); - db.users().insertProjectPermissionOnUser(user, "p1", project); - - ServerUserSession underTest = newAnonymousSession(); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", project)).isFalse(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_returns_true_for_any_project_or_permission_for_root_user() { - UserDto root = db.users().insertUser(); - root = db.users().makeRoot(root); - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - - ServerUserSession underTest = newUserSession(root); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, "does not matter", publicProject)).isTrue(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_keeps_cache_of_permissions_of_logged_in_user() { - UserDto user = db.users().insertUser(); - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, publicProject); - - UserSession underTest = newUserSession(user); - - // feed the cache - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ADMIN, publicProject)).isTrue(); - - // change permissions without updating the cache - db.users().deletePermissionFromUser(publicProject, user, UserRole.ADMIN); - db.users().insertProjectPermissionOnUser(user, UserRole.ISSUE_ADMIN, publicProject); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ADMIN, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ISSUE_ADMIN, publicProject)).isFalse(); - } - - @Test - public void hasComponentPermissionByDtoOrUuid_keeps_cache_of_permissions_of_anonymous_user() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - db.users().insertProjectPermissionOnAnyone(UserRole.ADMIN, publicProject); - - UserSession underTest = newAnonymousSession(); - - // feed the cache - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ADMIN, publicProject)).isTrue(); - - // change permissions without updating the cache - db.users().deleteProjectPermissionFromAnyone(publicProject, UserRole.ADMIN); - db.users().insertProjectPermissionOnAnyone(UserRole.ISSUE_ADMIN, publicProject); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ADMIN, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ISSUE_ADMIN, publicProject)).isFalse(); - } - - private boolean hasComponentPermissionByDtoOrUuid(UserSession underTest, String permission, ComponentDto component) { - boolean b1 = underTest.hasComponentPermission(permission, component); - boolean b2 = underTest.hasComponentUuidPermission(permission, component.uuid()); - checkState(b1 == b2, "Different behaviors"); - return b1; - } - - @Test - public void keepAuthorizedComponents_returns_empty_list_if_no_permissions_are_granted() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - ComponentDto privateProject = db.components().insertPrivateProject(organization); - - UserSession underTest = newAnonymousSession(); - - assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty(); - } - - @Test - public void keepAuthorizedComponents_filters_components_with_granted_permissions_for_logged_in_user() { - UserDto user = db.users().insertUser(); - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - ComponentDto privateProject = db.components().insertPrivateProject(organization); - db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, privateProject); - - UserSession underTest = newUserSession(user); - - assertThat(underTest.keepAuthorizedComponents(UserRole.ISSUE_ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty(); - assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject))).containsExactly(privateProject); - } - - @Test - public void keepAuthorizedComponents_filters_components_with_granted_permissions_for_anonymous() { - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - ComponentDto privateProject = db.components().insertPrivateProject(organization); - db.users().insertProjectPermissionOnAnyone(UserRole.ISSUE_ADMIN, publicProject); - - UserSession underTest = newAnonymousSession(); - - assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty(); - assertThat(underTest.keepAuthorizedComponents(UserRole.ISSUE_ADMIN, Arrays.asList(privateProject, publicProject))).containsExactly(publicProject); - } - - @Test - public void keepAuthorizedComponents_returns_all_specified_components_if_root() { - UserDto root = db.users().insertUser(); - root = db.users().makeRoot(root); - OrganizationDto organization = db.organizations().insert(); - ComponentDto publicProject = db.components().insertPublicProject(organization); - ComponentDto privateProject = db.components().insertPrivateProject(organization); - - UserSession underTest = newUserSession(root); - - assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject))) - .containsExactly(privateProject, publicProject); - } - - @Test - public void keepAuthorizedComponents_on_branches() { - UserDto user = db.users().insertUser(); - ComponentDto privateProject = db.components().insertPrivateProject(); - db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, privateProject); - ComponentDto privateBranchProject = db.components().insertProjectBranch(privateProject); - - UserSession underTest = newUserSession(user); - - assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, asList(privateProject, privateBranchProject))) - .containsExactlyInAnyOrder(privateProject, privateBranchProject); - } - - @Test - public void isSystemAdministrator_returns_true_if_org_feature_is_enabled_and_user_is_root() { - organizationFlags.setEnabled(true); - UserDto root = db.users().insertUser(); - root = db.users().makeRoot(root); - - UserSession session = newUserSession(root); - - assertThat(session.isSystemAdministrator()).isTrue(); - } - - @Test - public void isSystemAdministrator_returns_false_if_org_feature_is_enabled_and_user_is_not_root() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(); - - UserSession session = newUserSession(user); - - assertThat(session.isSystemAdministrator()).isFalse(); - } - - @Test - public void isSystemAdministrator_returns_false_if_org_feature_is_enabled_and_user_is_administrator_of_default_organization() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(); - db.users().insertPermissionOnUser(db.getDefaultOrganization(), user, SYSTEM_ADMIN); - - UserSession session = newUserSession(user); - - assertThat(session.isSystemAdministrator()).isFalse(); - } - - @Test - public void isSystemAdministrator_returns_true_if_org_feature_is_disabled_and_user_is_administrator_of_default_organization() { - organizationFlags.setEnabled(false); - UserDto user = db.users().insertUser(); - db.users().insertPermissionOnUser(db.getDefaultOrganization(), user, SYSTEM_ADMIN); - - UserSession session = newUserSession(user); - - assertThat(session.isSystemAdministrator()).isTrue(); - } - - @Test - public void isSystemAdministrator_returns_false_if_org_feature_is_disabled_and_user_is_not_administrator_of_default_organization() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(); - db.users().insertPermissionOnUser(db.getDefaultOrganization(), user, PROVISIONING); - - UserSession session = newUserSession(user); - - assertThat(session.isSystemAdministrator()).isFalse(); - } - - @Test - public void keep_isSystemAdministrator_flag_in_cache() { - organizationFlags.setEnabled(false); - UserDto user = db.users().insertUser(); - db.users().insertPermissionOnUser(db.getDefaultOrganization(), user, SYSTEM_ADMIN); - - UserSession session = newUserSession(user); - - session.checkIsSystemAdministrator(); - - db.getDbClient().userDao().deactivateUser(db.getSession(), user); - db.commit(); - - // should fail but succeeds because flag is kept in cache - session.checkIsSystemAdministrator(); - } - - @Test - public void checkIsSystemAdministrator_succeeds_if_system_administrator() { - organizationFlags.setEnabled(true); - UserDto root = db.users().insertUser(); - root = db.users().makeRoot(root); - - UserSession session = newUserSession(root); - - session.checkIsSystemAdministrator(); - } - - @Test - public void checkIsSystemAdministrator_throws_ForbiddenException_if_not_system_administrator() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertUser(); - - UserSession session = newUserSession(user); - - expectedException.expect(ForbiddenException.class); - expectedException.expectMessage("Insufficient privileges"); - - session.checkIsSystemAdministrator(); - } - - @Test - public void hasComponentPermission_on_branch_checks_permissions_of_its_project() { - UserDto user = db.users().insertUser(); - ComponentDto privateProject = db.components().insertPrivateProject(); - ComponentDto branch = db.components().insertProjectBranch(privateProject, b -> b.setKey("feature/foo")); - ComponentDto fileInBranch = db.components().insertComponent(newChildComponent("fileUuid", branch, branch)); - - // permissions are defined on the project, not on the branch - db.users().insertProjectPermissionOnUser(user, "p1", privateProject); - - UserSession underTest = newUserSession(user); - - assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", privateProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", branch)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", fileInBranch)).isTrue(); - } - - @Test - public void hasMembership() { - OrganizationDto organization = db.organizations().insert(); - UserDto notMember = db.users().insertUser(); - UserDto member = db.users().insertUser(); - db.organizations().addMember(organization, member); - UserDto root = db.users().makeRoot(db.users().insertUser()); - - assertThat(newUserSession(member).hasMembership(organization)).isTrue(); - assertThat(newUserSession(notMember).hasMembership(organization)).isFalse(); - assertThat(newUserSession(root).hasMembership(organization)).isTrue(); - } - - @Test - public void hasMembership_keeps_membership_in_cache() { - OrganizationDto organization = db.organizations().insert(); - UserDto user = db.users().insertUser(); - db.organizations().addMember(organization, user); - - ServerUserSession session = newUserSession(user); - assertThat(session.hasMembership(organization)).isTrue(); - - // membership updated but not cache - db.getDbClient().organizationMemberDao().delete(db.getSession(), organization.getUuid(), user.getId()); - db.commit(); - assertThat(session.hasMembership(organization)).isTrue(); - } - - @Test - public void checkMembership_throws_ForbiddenException_when_user_is_not_member_of_organization() { - OrganizationDto organization = db.organizations().insert(); - UserDto notMember = db.users().insertUser(); - - expectedException.expect(ForbiddenException.class); - expectedException.expectMessage(String.format("You're not member of organization '%s'", organization.getKey())); - - newUserSession(notMember).checkMembership(organization); - } - - @Test - public void checkMembership_succeeds_when_user_is_member_of_organization() { - OrganizationDto organization = db.organizations().insert(); - UserDto member = db.users().insertUser(); - db.organizations().addMember(organization, member); - - newUserSession(member).checkMembership(organization); - } - - @Test - public void checkMembership_succeeds_when_user_is_not_member_of_organization_but_root() { - OrganizationDto organization = db.organizations().insert(); - UserDto root = db.users().makeRoot(db.users().insertUser()); - - newUserSession(root).checkMembership(organization); - } - - private ServerUserSession newUserSession(@Nullable UserDto userDto) { - return new ServerUserSession(dbClient, organizationFlags, defaultOrganizationProvider, userDto); - } - - private ServerUserSession newAnonymousSession() { - return newUserSession(null); - } - - private void expectInsufficientPrivilegesForbiddenException() { - expectedException.expect(ForbiddenException.class); - expectedException.expectMessage("Insufficient privileges"); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/SystemPasscodeImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/SystemPasscodeImplTest.java deleted file mode 100644 index 352070eacf3..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/SystemPasscodeImplTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.utils.log.LogTester; -import org.sonar.api.utils.log.LoggerLevel; -import org.sonar.server.ws.TestRequest; - -import static org.assertj.core.api.Assertions.assertThat; - -public class SystemPasscodeImplTest { - - @Rule - public LogTester logTester = new LogTester(); - - private MapSettings settings = new MapSettings(); - private SystemPasscodeImpl underTest = new SystemPasscodeImpl(settings.asConfig()); - - @After - public void tearDown() { - underTest.stop(); - } - - @Test - public void startup_logs_show_that_feature_is_enabled() { - configurePasscode("foo"); - underTest.start(); - - assertThat(logTester.logs(LoggerLevel.INFO)).contains("System authentication by passcode is enabled"); - } - - @Test - public void startup_logs_show_that_feature_is_disabled() { - underTest.start(); - - assertThat(logTester.logs(LoggerLevel.INFO)).contains("System authentication by passcode is disabled"); - } - - @Test - public void passcode_is_disabled_if_blank_configuration() { - configurePasscode(""); - underTest.start(); - - assertThat(logTester.logs(LoggerLevel.INFO)).contains("System authentication by passcode is disabled"); - } - - @Test - public void isValid_is_true_if_request_header_matches_configured_passcode() { - verifyIsValid(true, "foo", "foo"); - } - - @Test - public void isValid_is_false_if_request_header_matches_configured_passcode_with_different_case() { - verifyIsValid(false, "foo", "FOO"); - } - - @Test - public void isValid_is_false_if_request_header_does_not_match_configured_passcode() { - verifyIsValid(false, "foo", "bar"); - } - - @Test - public void isValid_is_false_if_request_header_is_defined_but_passcode_is_not_configured() { - verifyIsValid(false, null, "foo"); - } - - @Test - public void isValid_is_false_if_request_header_is_empty() { - verifyIsValid(false, "foo", ""); - } - - private void verifyIsValid(boolean expectedResult, String configuredPasscode, String header) { - configurePasscode(configuredPasscode); - - TestRequest request = new TestRequest(); - request.setHeader("X-Sonar-Passcode", header); - - assertThat(underTest.isValid(request)).isEqualTo(expectedResult); - } - - private void configurePasscode(String propertyValue) { - settings.setProperty("sonar.web.systemPasscode", propertyValue); - underTest.start(); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/TestUserSessionFactory.java b/server/sonar-server/src/test/java/org/sonar/server/user/TestUserSessionFactory.java deleted file mode 100644 index 2dfa06ab569..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/TestUserSessionFactory.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import java.util.Collection; -import java.util.Optional; -import javax.annotation.Nullable; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.permission.OrganizationPermission; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; - -import static java.util.Objects.requireNonNull; - -/** - * Simple implementation of {@link UserSessionFactory}. It creates - * instances of {@link UserSession} that don't manage groups nor - * permissions. Only basic user information like {@link UserSession#isLoggedIn()} - * and {@link UserSession#getLogin()} are available. The methods - * relying on groups or permissions throw {@link UnsupportedOperationException}. - */ -public class TestUserSessionFactory implements UserSessionFactory { - - private TestUserSessionFactory() { - } - - @Override - public UserSession create(UserDto user) { - return new TestUserSession(requireNonNull(user)); - } - - @Override - public UserSession createAnonymous() { - return new TestUserSession(null); - } - - public static TestUserSessionFactory standalone() { - return new TestUserSessionFactory(); - } - - private static class TestUserSession extends AbstractUserSession { - private final UserDto user; - - public TestUserSession(@Nullable UserDto user) { - this.user = user; - } - - @Override - public String getLogin() { - return user != null ? user.getLogin() : null; - } - - @Override - public String getUuid() { - return user != null ? user.getUuid() : null; - } - - @Override - public String getName() { - return user != null ? user.getName() : null; - } - - @Override - public Integer getUserId() { - return user != null ? user.getId() : null; - } - - @Override - public Collection<GroupDto> getGroups() { - throw notImplemented(); - } - - @Override - public Optional<IdentityProvider> getIdentityProvider() { - throw notImplemented(); - } - - @Override - public Optional<ExternalIdentity> getExternalIdentity() { - throw notImplemented(); - } - - @Override - public boolean isLoggedIn() { - return user != null; - } - - @Override - public boolean isRoot() { - throw notImplemented(); - } - - @Override - protected boolean hasPermissionImpl(OrganizationPermission permission, String organizationUuid) { - throw notImplemented(); - } - - @Override - protected Optional<String> componentUuidToProjectUuid(String componentUuid) { - throw notImplemented(); - } - - @Override - protected boolean hasProjectUuidPermission(String permission, String projectUuid) { - throw notImplemented(); - } - - @Override - public boolean isSystemAdministrator() { - throw notImplemented(); - } - - @Override - public boolean hasMembershipImpl(OrganizationDto organizationDto) { - throw notImplemented(); - } - - private static RuntimeException notImplemented() { - return new UnsupportedOperationException("not implemented"); - } - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java deleted file mode 100644 index ec5face1ba2..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.GroupTesting; -import org.sonar.server.exceptions.UnauthorizedException; -import org.sonar.server.tester.AnonymousMockUserSession; -import org.sonar.server.tester.MockUserSession; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ThreadLocalUserSessionTest { - - private ThreadLocalUserSession threadLocalUserSession = new ThreadLocalUserSession(); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Before - public void setUp() { - // for test isolation - threadLocalUserSession.unload(); - } - - @After - public void tearDown() { - // clean up for next test - threadLocalUserSession.unload(); - } - - @Test - public void get_session_for_user() { - GroupDto group = GroupTesting.newGroupDto(); - MockUserSession expected = new MockUserSession("karadoc") - .setUuid("karadoc-uuid") - .setUserId(123) - .setGroups(group); - threadLocalUserSession.set(expected); - - UserSession session = threadLocalUserSession.get(); - assertThat(session).isSameAs(expected); - assertThat(threadLocalUserSession.getUserId()).isEqualTo(123); - assertThat(threadLocalUserSession.getLogin()).isEqualTo("karadoc"); - assertThat(threadLocalUserSession.getUuid()).isEqualTo("karadoc-uuid"); - assertThat(threadLocalUserSession.isLoggedIn()).isTrue(); - assertThat(threadLocalUserSession.getGroups()).extracting(GroupDto::getId).containsOnly(group.getId()); - } - - @Test - public void get_session_for_anonymous() { - AnonymousMockUserSession expected = new AnonymousMockUserSession(); - threadLocalUserSession.set(expected); - - UserSession session = threadLocalUserSession.get(); - assertThat(session).isSameAs(expected); - assertThat(threadLocalUserSession.getLogin()).isNull(); - assertThat(threadLocalUserSession.getUserId()).isNull(); - assertThat(threadLocalUserSession.isLoggedIn()).isFalse(); - assertThat(threadLocalUserSession.getGroups()).isEmpty(); - } - - @Test - public void throw_UnauthorizedException_when_no_session() { - thrown.expect(UnauthorizedException.class); - threadLocalUserSession.get(); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java deleted file mode 100644 index cfe9b2ac435..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java +++ /dev/null @@ -1,665 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Multimap; -import java.util.List; -import org.elasticsearch.search.SearchHit; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.ArgumentCaptor; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; -import org.sonar.api.platform.NewUserHandler; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserPropertyDto; -import org.sonar.server.authentication.CredentialsLocalAuthentication; -import org.sonar.server.authentication.CredentialsLocalAuthentication.HashMethod; -import org.sonar.server.es.EsTester; -import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.TestDefaultOrganizationProvider; -import org.sonar.server.organization.TestOrganizationFlags; -import org.sonar.server.user.index.UserIndexDefinition; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.data.MapEntry.entry; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.sonar.db.user.UserTesting.newLocalUser; -import static org.sonar.process.ProcessProperties.Property.ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS; -import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY; - -public class UserUpdaterCreateTest { - - private static final String DEFAULT_LOGIN = "marius"; - - private System2 system2 = new AlwaysIncreasingSystem2(); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Rule - public EsTester es = EsTester.create(); - - @Rule - public DbTester db = DbTester.create(system2); - - private DbClient dbClient = db.getDbClient(); - private NewUserNotifier newUserNotifier = mock(NewUserNotifier.class); - private ArgumentCaptor<NewUserHandler.Context> newUserHandler = ArgumentCaptor.forClass(NewUserHandler.Context.class); - private DbSession session = db.getSession(); - private UserIndexer userIndexer = new UserIndexer(dbClient, es.client()); - private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); - private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); - private MapSettings settings = new MapSettings(); - private CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient()); - - private UserUpdater underTest = new UserUpdater(system2, newUserNotifier, dbClient, userIndexer, organizationFlags, defaultOrganizationProvider, - new DefaultGroupFinder(dbClient), settings.asConfig(), localAuthentication); - - @Test - public void create_user() { - createDefaultGroup(); - - UserDto dto = underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setEmail("user@mail.com") - .setPassword("PASSWORD") - .setScmAccounts(ImmutableList.of("u1", "u_1", "User 1")) - .build(), u -> { - }); - - assertThat(dto.getId()).isNotNull(); - assertThat(dto.getLogin()).isEqualTo("user"); - assertThat(dto.getName()).isEqualTo("User"); - assertThat(dto.getEmail()).isEqualTo("user@mail.com"); - assertThat(dto.getScmAccountsAsList()).containsOnly("u1", "u_1", "User 1"); - assertThat(dto.isActive()).isTrue(); - assertThat(dto.isLocal()).isTrue(); - - assertThat(dto.getSalt()).isNull(); - assertThat(dto.getHashMethod()).isEqualTo(HashMethod.BCRYPT.name()); - assertThat(dto.getCryptedPassword()).isNotNull(); - assertThat(dto.getCreatedAt()) - .isPositive() - .isEqualTo(dto.getUpdatedAt()); - - assertThat(dbClient.userDao().selectByLogin(session, "user").getId()).isEqualTo(dto.getId()); - List<SearchHit> indexUsers = es.getDocuments(UserIndexDefinition.TYPE_USER); - assertThat(indexUsers).hasSize(1); - assertThat(indexUsers.get(0).getSourceAsMap()) - .contains( - entry("login", "user"), - entry("name", "User"), - entry("email", "user@mail.com")); - } - - @Test - public void create_user_with_minimum_fields() { - createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("us") - .setName("User") - .build(), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, "us"); - assertThat(dto.getId()).isNotNull(); - assertThat(dto.getLogin()).isEqualTo("us"); - assertThat(dto.getName()).isEqualTo("User"); - assertThat(dto.getEmail()).isNull(); - assertThat(dto.getScmAccounts()).isNull(); - assertThat(dto.isActive()).isTrue(); - } - - @Test - public void create_user_generates_unique_login_no_login_provided() { - createDefaultGroup(); - - UserDto user = underTest.createAndCommit(db.getSession(), NewUser.builder() - .setName("John Doe") - .build(), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, user.getLogin()); - assertThat(dto.getLogin()).startsWith("john-doe"); - assertThat(dto.getName()).isEqualTo("John Doe"); - } - - @Test - public void create_user_generates_unique_login_when_login_is_empty() { - createDefaultGroup(); - - UserDto user = underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("") - .setName("John Doe") - .build(), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, user.getLogin()); - assertThat(dto.getLogin()).startsWith("john-doe"); - assertThat(dto.getName()).isEqualTo("John Doe"); - } - - @Test - public void create_user_with_sq_authority_when_no_authority_set() { - createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setPassword("password") - .build(), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, "user"); - assertThat(dto.getExternalLogin()).isEqualTo("user"); - assertThat(dto.getExternalIdentityProvider()).isEqualTo("sonarqube"); - assertThat(dto.isLocal()).isTrue(); - } - - @Test - public void create_user_with_identity_provider() { - createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setExternalIdentity(new ExternalIdentity("github", "github-user", "ABCD")) - .build(), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, "user"); - assertThat(dto.isLocal()).isFalse(); - assertThat(dto.getExternalId()).isEqualTo("ABCD"); - assertThat(dto.getExternalLogin()).isEqualTo("github-user"); - assertThat(dto.getExternalIdentityProvider()).isEqualTo("github"); - assertThat(dto.getCryptedPassword()).isNull(); - assertThat(dto.getSalt()).isNull(); - } - - @Test - public void create_user_with_sonarqube_external_identity() { - createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setExternalIdentity(new ExternalIdentity(SQ_AUTHORITY, "user", "user")) - .build(), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, "user"); - assertThat(dto.isLocal()).isFalse(); - assertThat(dto.getExternalId()).isEqualTo("user"); - assertThat(dto.getExternalLogin()).isEqualTo("user"); - assertThat(dto.getExternalIdentityProvider()).isEqualTo("sonarqube"); - assertThat(dto.getCryptedPassword()).isNull(); - assertThat(dto.getSalt()).isNull(); - } - - @Test - public void create_user_with_scm_accounts_containing_blank_or_null_entries() { - createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setPassword("password") - .setScmAccounts(asList("u1", "", null)) - .build(), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, "user").getScmAccountsAsList()).containsOnly("u1"); - } - - @Test - public void create_user_with_scm_accounts_containing_one_blank_entry() { - createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setPassword("password") - .setScmAccounts(asList("")) - .build(), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, "user").getScmAccounts()).isNull(); - } - - @Test - public void create_user_with_scm_accounts_containing_duplications() { - createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setPassword("password") - .setScmAccounts(asList("u1", "u1")) - .build(), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, "user").getScmAccountsAsList()).containsOnly("u1"); - } - - @Test - public void create_not_onboarded_user_if_onboarding_setting_is_set_to_false() { - settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS.getKey(), false); - createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .build(), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, "user").isOnboarded()).isTrue(); - } - - @Test - public void create_onboarded_user_if_onboarding_setting_is_set_to_true() { - settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS.getKey(), true); - createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .build(), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, "user").isOnboarded()).isFalse(); - } - - @Test - public void set_notifications_readDate_setting_when_creating_user_and_organization_enabled() { - long now = system2.now(); - organizationFlags.setEnabled(true); - createDefaultGroup(); - - UserDto user = underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("userLogin") - .setName("UserName") - .build(), u -> { - }); - - UserPropertyDto notificationReadDateSetting = dbClient.userPropertiesDao().selectByUser(session, user).get(0); - assertThat(notificationReadDateSetting.getKey()).isEqualTo("notifications.readDate"); - assertThat(Long.parseLong(notificationReadDateSetting.getValue())).isGreaterThanOrEqualTo(now); - } - - @Test - public void does_not_set_notifications_readDate_setting_when_creating_user_when_not_on_and_organization_disabled() { - createDefaultGroup(); - - UserDto user = underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("userLogin") - .setName("UserName") - .build(), u -> { - }); - - assertThat(dbClient.userPropertiesDao().selectByUser(session, user)).isEmpty(); - } - - @Test - public void create_user_and_index_other_user() { - createDefaultGroup(); - UserDto otherUser = db.users().insertUser(); - - UserDto created = underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setEmail("user@mail.com") - .setPassword("PASSWORD") - .build(), u -> { - }, otherUser); - - assertThat(es.getIds(UserIndexDefinition.TYPE_USER)).containsExactlyInAnyOrder(created.getUuid(), otherUser.getUuid()); - } - - @Test - public void fail_to_create_user_with_invalid_login() { - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Use only letters, numbers, and .-_@ please."); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("/marius/") - .setName("Marius") - .setEmail("marius@mail.com") - .setPassword("password") - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_with_space_in_login() { - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Use only letters, numbers, and .-_@ please."); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("mari us") - .setName("Marius") - .setEmail("marius@mail.com") - .setPassword("password") - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_with_too_short_login() { - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Login is too short (minimum is 2 characters)"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("m") - .setName("Marius") - .setEmail("marius@mail.com") - .setPassword("password") - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_with_too_long_login() { - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Login is too long (maximum is 255 characters)"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin(Strings.repeat("m", 256)) - .setName("Marius") - .setEmail("marius@mail.com") - .setPassword("password") - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_with_missing_name() { - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Name can't be empty"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin(DEFAULT_LOGIN) - .setName(null) - .setEmail("marius@mail.com") - .setPassword("password") - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_with_too_long_name() { - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Name is too long (maximum is 200 characters)"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin(DEFAULT_LOGIN) - .setName(Strings.repeat("m", 201)) - .setEmail("marius@mail.com") - .setPassword("password") - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_with_too_long_email() { - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Email is too long (maximum is 100 characters)"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin(DEFAULT_LOGIN) - .setName("Marius") - .setEmail(Strings.repeat("m", 101)) - .setPassword("password") - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_with_many_errors() { - try { - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("") - .setName("") - .setEmail("marius@mail.com") - .setPassword("") - .build(), u -> { - }); - fail(); - } catch (BadRequestException e) { - assertThat(e.errors()).containsExactlyInAnyOrder("Name can't be empty", "Password can't be empty"); - } - } - - @Test - public void fail_to_create_user_when_scm_account_is_already_used() { - db.users().insertUser(newLocalUser("john", "John", null).setScmAccounts(singletonList("jo"))); - - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("The scm account 'jo' is already used by user(s) : 'John (john)'"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin(DEFAULT_LOGIN) - .setName("Marius") - .setEmail("marius@mail.com") - .setPassword("password") - .setScmAccounts(asList("jo")) - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_when_scm_account_is_already_used_by_many_users() { - db.users().insertUser(newLocalUser("john", "John", null).setScmAccounts(singletonList("john@email.com"))); - db.users().insertUser(newLocalUser("technical-account", "Technical account", null).setScmAccounts(singletonList("john@email.com"))); - - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("The scm account 'john@email.com' is already used by user(s) : 'John (john), Technical account (technical-account)'"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin(DEFAULT_LOGIN) - .setName("Marius") - .setEmail("marius@mail.com") - .setPassword("password") - .setScmAccounts(asList("john@email.com")) - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_when_scm_account_is_user_login() { - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Login and email are automatically considered as SCM accounts"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin(DEFAULT_LOGIN) - .setName("Marius2") - .setEmail("marius2@mail.com") - .setPassword("password2") - .setScmAccounts(asList(DEFAULT_LOGIN)) - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_when_scm_account_is_user_email() { - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Login and email are automatically considered as SCM accounts"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin(DEFAULT_LOGIN) - .setName("Marius2") - .setEmail("marius2@mail.com") - .setPassword("password2") - .setScmAccounts(asList("marius2@mail.com")) - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_when_login_already_exists() { - createDefaultGroup(); - UserDto existingUser = db.users().insertUser(u -> u.setLogin("existing_login")); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A user with login 'existing_login' already exists"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin(existingUser.getLogin()) - .setName("User") - .setPassword("PASSWORD") - .build(), u -> { - }); - } - - @Test - public void fail_to_create_user_when_external_id_and_external_provider_already_exists() { - createDefaultGroup(); - UserDto existingUser = db.users().insertUser(u -> u.setExternalId("existing_external_id").setExternalIdentityProvider("existing_external_provider")); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A user with provider id 'existing_external_id' and identity provider 'existing_external_provider' already exists"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("new_login") - .setName("User") - .setExternalIdentity(new ExternalIdentity(existingUser.getExternalIdentityProvider(), existingUser.getExternalLogin(), existingUser.getExternalId())) - .build(), u -> { - }); - } - - @Test - public void notify_new_user() { - createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setEmail("user@mail.com") - .setPassword("password") - .setScmAccounts(asList("u1", "u_1")) - .build(), u -> { - }); - - verify(newUserNotifier).onNewUser(newUserHandler.capture()); - assertThat(newUserHandler.getValue().getLogin()).isEqualTo("user"); - assertThat(newUserHandler.getValue().getName()).isEqualTo("User"); - assertThat(newUserHandler.getValue().getEmail()).isEqualTo("user@mail.com"); - } - - @Test - public void associate_default_group_when_creating_user_and_organizations_are_disabled() { - GroupDto defaultGroup = createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setEmail("user@mail.com") - .setPassword("password") - .build(), u -> { - }); - - Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList("user")); - assertThat(groups.get("user")).containsOnly(defaultGroup.getName()); - } - - @Test - public void does_not_associate_default_group_when_creating_user_and_organizations_are_enabled() { - organizationFlags.setEnabled(true); - createDefaultGroup(); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setEmail("user@mail.com") - .setPassword("password") - .build(), u -> { - }); - - Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList("user")); - assertThat(groups.get("user")).isEmpty(); - } - - @Test - public void fail_to_associate_default_group_when_default_group_does_not_exist() { - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Default group cannot be found"); - - underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setEmail("user@mail.com") - .setPassword("password") - .setScmAccounts(asList("u1", "u_1")) - .build(), u -> { - }); - } - - @Test - public void add_user_as_member_of_default_organization_when_creating_user_and_organizations_are_disabled() { - createDefaultGroup(); - - UserDto dto = underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setEmail("user@mail.com") - .setPassword("PASSWORD") - .build(), u -> { - }); - - assertThat(dbClient.organizationMemberDao().select(db.getSession(), defaultOrganizationProvider.get().getUuid(), dto.getId())).isPresent(); - } - - @Test - public void does_not_add_user_as_member_of_default_organization_when_creating_user_and_organizations_are_enabled() { - organizationFlags.setEnabled(true); - createDefaultGroup(); - - UserDto dto = underTest.createAndCommit(db.getSession(), NewUser.builder() - .setLogin("user") - .setName("User") - .setEmail("user@mail.com") - .setPassword("PASSWORD") - .build(), u -> { - }); - - assertThat(dbClient.organizationMemberDao().select(db.getSession(), defaultOrganizationProvider.get().getUuid(), dto.getId())).isNotPresent(); - } - - private GroupDto createDefaultGroup() { - return db.users().insertDefaultGroup(db.getDefaultOrganization()); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterReactivateTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterReactivateTest.java deleted file mode 100644 index d280c1af117..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterReactivateTest.java +++ /dev/null @@ -1,386 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import com.google.common.collect.Multimap; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.GroupTesting; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserPropertyDto; -import org.sonar.server.authentication.CredentialsLocalAuthentication; -import org.sonar.server.authentication.CredentialsLocalAuthentication.HashMethod; -import org.sonar.server.es.EsTester; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.TestDefaultOrganizationProvider; -import org.sonar.server.organization.TestOrganizationFlags; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static java.lang.String.format; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.sonar.process.ProcessProperties.Property.ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS; - -public class UserUpdaterReactivateTest { - - private System2 system2 = new AlwaysIncreasingSystem2(); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Rule - public EsTester es = EsTester.create(); - - @Rule - public DbTester db = DbTester.create(system2); - - private DbClient dbClient = db.getDbClient(); - private NewUserNotifier newUserNotifier = mock(NewUserNotifier.class); - private DbSession session = db.getSession(); - private UserIndexer userIndexer = new UserIndexer(dbClient, es.client()); - private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); - private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); - private MapSettings settings = new MapSettings(); - private CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient()); - private UserUpdater underTest = new UserUpdater(system2, newUserNotifier, dbClient, userIndexer, organizationFlags, defaultOrganizationProvider, - new DefaultGroupFinder(dbClient), settings.asConfig(), localAuthentication);; - - @Test - public void reactivate_user() { - UserDto user = db.users().insertUser(u -> u.setActive(false)); - createDefaultGroup(); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin("marius") - .setName("Marius2") - .setEmail("marius2@mail.com") - .setPassword("password2") - .build(), - u -> { - }); - - UserDto reloaded = dbClient.userDao().selectByUuid(session, user.getUuid()); - assertThat(reloaded.isActive()).isTrue(); - assertThat(reloaded.getLogin()).isEqualTo("marius"); - assertThat(reloaded.getName()).isEqualTo("Marius2"); - assertThat(reloaded.getEmail()).isEqualTo("marius2@mail.com"); - assertThat(reloaded.getScmAccounts()).isNull(); - assertThat(reloaded.isLocal()).isTrue(); - assertThat(reloaded.getSalt()).isNull(); - assertThat(reloaded.getHashMethod()).isEqualTo(HashMethod.BCRYPT.name()); - assertThat(reloaded.getCryptedPassword()).isNotNull().isNotEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"); - assertThat(reloaded.getCreatedAt()).isEqualTo(user.getCreatedAt()); - assertThat(reloaded.getUpdatedAt()).isGreaterThan(user.getCreatedAt()); - } - - @Test - public void reactivate_user_without_providing_login() { - UserDto user = db.users().insertUser(u -> u.setActive(false)); - createDefaultGroup(); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setName("Marius2") - .setEmail("marius2@mail.com") - .setPassword("password2") - .build(), - u -> { - }); - - UserDto reloaded = dbClient.userDao().selectByUuid(session, user.getUuid()); - assertThat(reloaded.isActive()).isTrue(); - assertThat(reloaded.getLogin()).isEqualTo(user.getLogin()); - } - - @Test - public void reactivate_user_not_having_password() { - UserDto user = db.users().insertDisabledUser(u -> u.setSalt(null).setCryptedPassword(null)); - createDefaultGroup(); - - UserDto dto = underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin(user.getLogin()) - .setName(user.getName()) - .build(), - u -> { - }); - - assertThat(dto.isActive()).isTrue(); - assertThat(dto.getName()).isEqualTo(user.getName()); - assertThat(dto.getScmAccounts()).isNull(); - assertThat(dto.getSalt()).isNull(); - assertThat(dto.getCryptedPassword()).isNull(); - assertThat(dto.getCreatedAt()).isEqualTo(user.getCreatedAt()); - assertThat(dto.getUpdatedAt()).isGreaterThan(user.getCreatedAt()); - } - - @Test - public void reactivate_user_with_external_provider() { - UserDto user = db.users().insertDisabledUser(u -> u.setLocal(true)); - createDefaultGroup(); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin(user.getLogin()) - .setName(user.getName()) - .setExternalIdentity(new ExternalIdentity("github", "john", "ABCD")) - .build(), u -> { - }); - session.commit(); - - UserDto dto = dbClient.userDao().selectByUuid(session, user.getUuid()); - assertThat(dto.isLocal()).isFalse(); - assertThat(dto.getExternalId()).isEqualTo("ABCD"); - assertThat(dto.getExternalLogin()).isEqualTo("john"); - assertThat(dto.getExternalIdentityProvider()).isEqualTo("github"); - } - - @Test - public void reactivate_user_using_same_external_info_but_was_local() { - UserDto user = db.users().insertDisabledUser(u -> u.setLocal(true) - .setExternalId("ABCD") - .setExternalLogin("john") - .setExternalIdentityProvider("github")); - createDefaultGroup(); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin(user.getLogin()) - .setName(user.getName()) - .setExternalIdentity(new ExternalIdentity("github", "john", "ABCD")) - .build(), u -> { - }); - session.commit(); - - UserDto dto = dbClient.userDao().selectByUuid(session, user.getUuid()); - assertThat(dto.isLocal()).isFalse(); - assertThat(dto.getExternalId()).isEqualTo("ABCD"); - assertThat(dto.getExternalLogin()).isEqualTo("john"); - assertThat(dto.getExternalIdentityProvider()).isEqualTo("github"); - } - - @Test - public void reactivate_user_with_local_provider() { - UserDto user = db.users().insertDisabledUser(u -> u.setLocal(false) - .setExternalId("ABCD") - .setExternalLogin("john") - .setExternalIdentityProvider("github")); - createDefaultGroup(); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin(user.getLogin()) - .setName(user.getName()) - .build(), u -> { - }); - session.commit(); - - UserDto dto = dbClient.userDao().selectByUuid(session, user.getUuid()); - assertThat(dto.isLocal()).isTrue(); - assertThat(dto.getExternalId()).isEqualTo(user.getLogin()); - assertThat(dto.getExternalLogin()).isEqualTo(user.getLogin()); - assertThat(dto.getExternalIdentityProvider()).isEqualTo("sonarqube"); - } - - @Test - public void fail_to_reactivate_user_if_active() { - UserDto user = db.users().insertUser(); - createDefaultGroup(); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage(format("An active user with login '%s' already exists", user.getLogin())); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin(user.getLogin()) - .setName(user.getName()) - .build(), u -> { - }); - } - - @Test - public void associate_default_groups_when_reactivating_user_and_organizations_are_disabled() { - UserDto userDto = db.users().insertDisabledUser(); - db.organizations().insertForUuid("org1"); - GroupDto groupDto = db.users().insertGroup(GroupTesting.newGroupDto().setName("sonar-devs").setOrganizationUuid("org1")); - db.users().insertMember(groupDto, userDto); - GroupDto defaultGroup = createDefaultGroup(); - - underTest.reactivateAndCommit(db.getSession(), userDto, NewUser.builder() - .setLogin(userDto.getLogin()) - .setName(userDto.getName()) - .build(), u -> { - }); - session.commit(); - - Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, singletonList(userDto.getLogin())); - assertThat(groups.get(userDto.getLogin()).stream().anyMatch(g -> g.equals(defaultGroup.getName()))).isTrue(); - } - - @Test - public void does_not_associate_default_groups_when_reactivating_user_and_organizations_are_enabled() { - organizationFlags.setEnabled(true); - UserDto userDto = db.users().insertDisabledUser(); - db.organizations().insertForUuid("org1"); - GroupDto groupDto = db.users().insertGroup(GroupTesting.newGroupDto().setName("sonar-devs").setOrganizationUuid("org1")); - db.users().insertMember(groupDto, userDto); - GroupDto defaultGroup = createDefaultGroup(); - - underTest.reactivateAndCommit(db.getSession(), userDto, NewUser.builder() - .setLogin(userDto.getLogin()) - .setName(userDto.getName()) - .build(), u -> { - }); - session.commit(); - - Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, singletonList(userDto.getLogin())); - assertThat(groups.get(userDto.getLogin()).stream().anyMatch(g -> g.equals(defaultGroup.getName()))).isFalse(); - } - - @Test - public void add_user_as_member_of_default_organization_when_reactivating_user_and_organizations_are_disabled() { - UserDto user = db.users().insertDisabledUser(); - createDefaultGroup(); - - UserDto dto = underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder().setLogin(user.getLogin()).setName(user.getName()).build(), u -> { - }); - - assertThat(dbClient.organizationMemberDao().select(db.getSession(), defaultOrganizationProvider.get().getUuid(), dto.getId())).isPresent(); - } - - @Test - public void does_not_add_user_as_member_of_default_organization_when_reactivating_user_and_organizations_are_enabled() { - organizationFlags.setEnabled(true); - UserDto user = db.users().insertDisabledUser(); - createDefaultGroup(); - - UserDto dto = underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder().setLogin(user.getLogin()).setName(user.getName()).build(), u -> { - }); - - assertThat(dbClient.organizationMemberDao().select(db.getSession(), defaultOrganizationProvider.get().getUuid(), dto.getId())).isNotPresent(); - } - - @Test - public void reactivate_not_onboarded_user_if_onboarding_setting_is_set_to_false() { - settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS.getKey(), false); - UserDto user = db.users().insertDisabledUser(u -> u.setOnboarded(false)); - createDefaultGroup(); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin(user.getLogin()) - .setName(user.getName()) - .build(), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, user.getLogin()).isOnboarded()).isTrue(); - } - - @Test - public void reactivate_onboarded_user_if_onboarding_setting_is_set_to_true() { - settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS.getKey(), true); - UserDto user = db.users().insertDisabledUser(u -> u.setOnboarded(true)); - createDefaultGroup(); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin(user.getLogin()) - .setName(user.getName()) - .build(), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, user.getLogin()).isOnboarded()).isFalse(); - } - - @Test - public void set_notifications_readDate_setting_when_reactivating_user_on_sonar_cloud() { - long now = system2.now(); - organizationFlags.setEnabled(true); - createDefaultGroup(); - UserDto user = db.users().insertDisabledUser(); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin(user.getLogin()) - .setName(user.getName()) - .build(), u -> { - }); - - UserPropertyDto notificationReadDateSetting = dbClient.userPropertiesDao().selectByUser(session, user).get(0); - assertThat(notificationReadDateSetting.getKey()).isEqualTo("notifications.readDate"); - assertThat(Long.parseLong(notificationReadDateSetting.getValue())).isGreaterThanOrEqualTo(now); - } - - @Test - public void does_not_set_notifications_readDate_setting_when_reactivating_user_when_not_on_sonar_cloud() { - createDefaultGroup(); - UserDto user = db.users().insertDisabledUser(); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin(user.getLogin()) - .setName(user.getName()) - .build(), u -> { - }); - - assertThat(dbClient.userPropertiesDao().selectByUser(session, user)).isEmpty(); - } - - @Test - public void fail_to_reactivate_user_when_login_already_exists() { - createDefaultGroup(); - UserDto user = db.users().insertUser(u -> u.setActive(false)); - UserDto existingUser = db.users().insertUser(u -> u.setLogin("existing_login")); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A user with login 'existing_login' already exists"); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin(existingUser.getLogin()) - .setName("Marius2") - .setPassword("password2") - .build(), - u -> { - }); - } - - @Test - public void fail_to_reactivate_user_when_external_id_and_external_provider_already_exists() { - createDefaultGroup(); - UserDto user = db.users().insertUser(u -> u.setActive(false)); - UserDto existingUser = db.users().insertUser(u -> u.setExternalId("existing_external_id").setExternalIdentityProvider("existing_external_provider")); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A user with provider id 'existing_external_id' and identity provider 'existing_external_provider' already exists"); - - underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() - .setLogin(user.getLogin()) - .setName("Marius2") - .setExternalIdentity(new ExternalIdentity(existingUser.getExternalIdentityProvider(), existingUser.getExternalLogin(), existingUser.getExternalId())) - .build(), - u -> { - }); - } - - private GroupDto createDefaultGroup() { - return db.users().insertDefaultGroup(db.getDefaultOrganization()); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java deleted file mode 100644 index 6e742c79a0b..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java +++ /dev/null @@ -1,636 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.user; - -import com.google.common.collect.Multimap; -import java.util.List; -import org.elasticsearch.search.SearchHit; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.property.PropertyDto; -import org.sonar.db.property.PropertyQuery; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.CredentialsLocalAuthentication; -import org.sonar.server.es.EsTester; -import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.organization.DefaultOrganizationProvider; -import org.sonar.server.organization.OrganizationUpdater; -import org.sonar.server.organization.TestDefaultOrganizationProvider; -import org.sonar.server.organization.TestOrganizationFlags; -import org.sonar.server.user.index.UserIndexDefinition; -import org.sonar.server.user.index.UserIndexer; -import org.sonar.server.usergroups.DefaultGroupFinder; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; -import static org.assertj.core.data.MapEntry.entry; -import static org.mockito.Mockito.mock; -import static org.sonar.api.CoreProperties.DEFAULT_ISSUE_ASSIGNEE; -import static org.sonar.db.user.UserTesting.newExternalUser; -import static org.sonar.db.user.UserTesting.newLocalUser; -import static org.sonar.db.user.UserTesting.newUserDto; - -public class UserUpdaterUpdateTest { - - private static final String DEFAULT_LOGIN = "marius"; - - private System2 system2 = new AlwaysIncreasingSystem2(); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Rule - public EsTester es = EsTester.create(); - - @Rule - public DbTester db = DbTester.create(system2); - - private DbClient dbClient = db.getDbClient(); - private NewUserNotifier newUserNotifier = mock(NewUserNotifier.class); - private DbSession session = db.getSession(); - private UserIndexer userIndexer = new UserIndexer(dbClient, es.client()); - private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); - private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); - private MapSettings settings = new MapSettings(); - private CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient()); - private UserUpdater underTest = new UserUpdater(system2, newUserNotifier, dbClient, userIndexer, organizationFlags, defaultOrganizationProvider, - new DefaultGroupFinder(dbClient), settings.asConfig(), localAuthentication); - - @Test - public void update_user() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com") - .setScmAccounts(asList("ma", "marius33"))); - createDefaultGroup(); - userIndexer.indexOnStartup(null); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setName("Marius2") - .setEmail("marius2@mail.com") - .setScmAccounts(singletonList("ma2")), u -> { - }); - - UserDto updatedUser = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN); - assertThat(updatedUser.isActive()).isTrue(); - assertThat(updatedUser.getName()).isEqualTo("Marius2"); - assertThat(updatedUser.getEmail()).isEqualTo("marius2@mail.com"); - assertThat(updatedUser.getScmAccountsAsList()).containsOnly("ma2"); - assertThat(updatedUser.getCreatedAt()).isEqualTo(user.getCreatedAt()); - assertThat(updatedUser.getUpdatedAt()).isGreaterThan(user.getCreatedAt()); - - List<SearchHit> indexUsers = es.getDocuments(UserIndexDefinition.TYPE_USER); - assertThat(indexUsers).hasSize(1); - assertThat(indexUsers.get(0).getSourceAsMap()) - .contains( - entry("login", DEFAULT_LOGIN), - entry("name", "Marius2"), - entry("email", "marius2@mail.com")); - } - - @Test - public void update_user_external_identity_when_user_was_not_local() { - UserDto user = db.users().insertUser(newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setName("Marius2") - .setEmail("marius2@email.com") - .setExternalIdentity(new ExternalIdentity("github", "john", "ABCD")), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN); - assertThat(dto.getExternalId()).isEqualTo("ABCD"); - assertThat(dto.getExternalLogin()).isEqualTo("john"); - assertThat(dto.getExternalIdentityProvider()).isEqualTo("github"); - assertThat(dto.getUpdatedAt()).isGreaterThan(user.getCreatedAt()); - } - - @Test - public void update_user_external_identity_when_user_was_local() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setName("Marius2") - .setEmail("marius2@email.com") - .setExternalIdentity(new ExternalIdentity("github", "john", "ABCD")), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN); - assertThat(dto.getExternalId()).isEqualTo("ABCD"); - assertThat(dto.getExternalLogin()).isEqualTo("john"); - assertThat(dto.getExternalIdentityProvider()).isEqualTo("github"); - // Password must be removed - assertThat(dto.getCryptedPassword()).isNull(); - assertThat(dto.getSalt()).isNull(); - assertThat(dto.getUpdatedAt()).isGreaterThan(user.getCreatedAt()); - } - - @Test - public void update_user_with_scm_accounts_containing_blank_entry() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr") - .setScmAccounts(asList("ma", "marius33"))); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setName("Marius2") - .setEmail("marius2@mail.com") - .setPassword("password2") - .setScmAccounts(asList("ma2", "", null)), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN); - assertThat(dto.getScmAccountsAsList()).containsOnly("ma2"); - } - - @Test - public void update_only_login_of_local_account() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setLogin("new_login"), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN)).isNull(); - UserDto userReloaded = dbClient.userDao().selectByUuid(session, user.getUuid()); - assertThat(userReloaded.getLogin()).isEqualTo("new_login"); - assertThat(userReloaded.getExternalIdentityProvider()).isEqualTo("sonarqube"); - assertThat(userReloaded.getExternalLogin()).isEqualTo("new_login"); - assertThat(userReloaded.getExternalId()).isEqualTo("new_login"); - // Following fields has not changed - assertThat(userReloaded.isLocal()).isTrue(); - assertThat(userReloaded.getName()).isEqualTo(user.getName()); - assertThat(userReloaded.getEmail()).isEqualTo(user.getEmail()); - assertThat(userReloaded.getScmAccountsAsList()).containsAll(user.getScmAccountsAsList()); - assertThat(userReloaded.getSalt()).isEqualTo(user.getSalt()); - assertThat(userReloaded.getCryptedPassword()).isEqualTo(user.getCryptedPassword()); - } - - @Test - public void update_only_login_of_external_account() { - UserDto user = db.users().insertUser(newExternalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setLogin("new_login"), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN)).isNull(); - UserDto userReloaded = dbClient.userDao().selectByUuid(session, user.getUuid()); - assertThat(userReloaded.getLogin()).isEqualTo("new_login"); - // Following fields has not changed - assertThat(userReloaded.isLocal()).isFalse(); - assertThat(userReloaded.getExternalLogin()).isEqualTo(user.getExternalLogin()); - assertThat(userReloaded.getExternalId()).isEqualTo(user.getExternalId()); - assertThat(userReloaded.getName()).isEqualTo(user.getName()); - assertThat(userReloaded.getEmail()).isEqualTo(user.getEmail()); - assertThat(userReloaded.getScmAccountsAsList()).containsAll(user.getScmAccountsAsList()); - assertThat(userReloaded.getSalt()).isEqualTo(user.getSalt()); - assertThat(userReloaded.getCryptedPassword()).isEqualTo(user.getCryptedPassword()); - } - - @Test - public void update_index_when_updating_user_login() { - UserDto oldUser = db.users().insertUser(); - createDefaultGroup(); - userIndexer.indexOnStartup(null); - - underTest.updateAndCommit(session, oldUser, new UpdateUser() - .setLogin("new_login"), u -> { - }); - - List<SearchHit> indexUsers = es.getDocuments(UserIndexDefinition.TYPE_USER); - assertThat(indexUsers).hasSize(1); - assertThat(indexUsers.get(0).getSourceAsMap()) - .contains(entry("login", "new_login")); - } - - @Test - public void update_default_assignee_when_updating_login() { - createDefaultGroup(); - UserDto oldUser = db.users().insertUser(); - ComponentDto project1 = db.components().insertPrivateProject(); - ComponentDto project2 = db.components().insertPrivateProject(); - ComponentDto anotherProject = db.components().insertPrivateProject(); - db.properties().insertProperties( - new PropertyDto().setKey(DEFAULT_ISSUE_ASSIGNEE).setValue(oldUser.getLogin()), - new PropertyDto().setKey(DEFAULT_ISSUE_ASSIGNEE).setValue(oldUser.getLogin()).setResourceId(project1.getId()), - new PropertyDto().setKey(DEFAULT_ISSUE_ASSIGNEE).setValue(oldUser.getLogin()).setResourceId(project2.getId()), - new PropertyDto().setKey(DEFAULT_ISSUE_ASSIGNEE).setValue("another login").setResourceId(anotherProject.getId())); - userIndexer.indexOnStartup(null); - - underTest.updateAndCommit(session, oldUser, new UpdateUser() - .setLogin("new_login"), u -> { - }); - - assertThat(db.getDbClient().propertiesDao().selectByQuery(PropertyQuery.builder().setKey(DEFAULT_ISSUE_ASSIGNEE).build(), db.getSession())) - .extracting(PropertyDto::getValue, PropertyDto::getResourceId) - .containsOnly( - tuple("new_login", null), - tuple("new_login", project1.getId()), - tuple("new_login", project2.getId()), - tuple("another login", anotherProject.getId())); - } - - @Test - public void update_only_user_name() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr") - .setScmAccounts(asList("ma", "marius33")) - .setSalt("salt") - .setCryptedPassword("crypted password")); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setName("Marius2"), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN); - assertThat(dto.getName()).isEqualTo("Marius2"); - - // Following fields has not changed - assertThat(dto.getEmail()).isEqualTo("marius@lesbronzes.fr"); - assertThat(dto.getScmAccountsAsList()).containsOnly("ma", "marius33"); - assertThat(dto.getSalt()).isEqualTo("salt"); - assertThat(dto.getCryptedPassword()).isEqualTo("crypted password"); - } - - @Test - public void update_only_user_email() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr") - .setScmAccounts(asList("ma", "marius33")) - .setSalt("salt") - .setCryptedPassword("crypted password")); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setEmail("marius2@mail.com"), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN); - assertThat(dto.getEmail()).isEqualTo("marius2@mail.com"); - - // Following fields has not changed - assertThat(dto.getName()).isEqualTo("Marius"); - assertThat(dto.getScmAccountsAsList()).containsOnly("ma", "marius33"); - assertThat(dto.getSalt()).isEqualTo("salt"); - assertThat(dto.getCryptedPassword()).isEqualTo("crypted password"); - } - - @Test - public void update_only_scm_accounts() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr") - .setScmAccounts(asList("ma", "marius33")) - .setSalt("salt") - .setCryptedPassword("crypted password")); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setScmAccounts(asList("ma2")), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN); - assertThat(dto.getScmAccountsAsList()).containsOnly("ma2"); - - // Following fields has not changed - assertThat(dto.getName()).isEqualTo("Marius"); - assertThat(dto.getEmail()).isEqualTo("marius@lesbronzes.fr"); - assertThat(dto.getSalt()).isEqualTo("salt"); - assertThat(dto.getCryptedPassword()).isEqualTo("crypted password"); - } - - @Test - public void update_scm_accounts_with_same_values() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr") - .setScmAccounts(asList("ma", "marius33"))); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setScmAccounts(asList("ma", "marius33")), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN); - assertThat(dto.getScmAccountsAsList()).containsOnly("ma", "marius33"); - } - - @Test - public void remove_scm_accounts() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr") - .setScmAccounts(asList("ma", "marius33"))); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setScmAccounts(null), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN); - assertThat(dto.getScmAccounts()).isNull(); - } - - @Test - public void update_only_user_password() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr") - .setScmAccounts(asList("ma", "marius33")) - .setSalt("salt") - .setCryptedPassword("crypted password")); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setPassword("password2"), u -> { - }); - - UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN); - assertThat(dto.getSalt()).isNotEqualTo("salt"); - assertThat(dto.getCryptedPassword()).isNotEqualTo("crypted password"); - - // Following fields has not changed - assertThat(dto.getName()).isEqualTo("Marius"); - assertThat(dto.getScmAccountsAsList()).containsOnly("ma", "marius33"); - assertThat(dto.getEmail()).isEqualTo("marius@lesbronzes.fr"); - } - - @Test - public void update_only_external_id() { - UserDto user = db.users().insertUser(newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com") - .setExternalId("1234") - .setExternalLogin("john.smith") - .setExternalIdentityProvider("github")); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser().setExternalIdentity(new ExternalIdentity("github", "john.smith", "ABCD")), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN)) - .extracting(UserDto::getExternalId) - .isEqualTo("ABCD"); - } - - @Test - public void update_only_external_login() { - UserDto user = db.users().insertUser(newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com") - .setExternalId("ABCD") - .setExternalLogin("john") - .setExternalIdentityProvider("github")); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser().setExternalIdentity(new ExternalIdentity("github", "john.smith", "ABCD")), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN)) - .extracting(UserDto::getExternalLogin, UserDto::getExternalIdentityProvider) - .containsOnly("john.smith", "github"); - } - - @Test - public void update_only_external_identity_provider() { - UserDto user = db.users().insertUser(newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com") - .setExternalId("ABCD") - .setExternalLogin("john") - .setExternalIdentityProvider("github")); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser().setExternalIdentity(new ExternalIdentity("bitbucket", "john", "ABCD")), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN)) - .extracting(UserDto::getExternalLogin, UserDto::getExternalIdentityProvider) - .containsOnly("john", "bitbucket"); - } - - @Test - public void does_not_update_user_when_no_change() { - UserDto user = newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com") - .setScmAccounts(asList("ma1", "ma2")); - db.users().insertUser(user); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setName(user.getName()) - .setEmail(user.getEmail()) - .setScmAccounts(user.getScmAccountsAsList()) - .setExternalIdentity(new ExternalIdentity(user.getExternalIdentityProvider(), user.getExternalLogin(), user.getExternalId())), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN).getUpdatedAt()).isEqualTo(user.getUpdatedAt()); - } - - @Test - public void does_not_update_user_when_no_change_and_scm_account_reordered() { - UserDto user = newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com") - .setScmAccounts(asList("ma1", "ma2")); - db.users().insertUser(user); - createDefaultGroup(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setName(user.getName()) - .setEmail(user.getEmail()) - .setScmAccounts(asList("ma2", "ma1")) - .setExternalIdentity(new ExternalIdentity(user.getExternalIdentityProvider(), user.getExternalLogin(), user.getExternalId())), u -> { - }); - - assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN).getUpdatedAt()).isEqualTo(user.getUpdatedAt()); - } - - @Test - public void update_user_and_index_other_user() { - createDefaultGroup(); - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com") - .setScmAccounts(asList("ma", "marius33"))); - UserDto otherUser = db.users().insertUser(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setName("Marius2") - .setEmail("marius2@mail.com") - .setPassword("password2") - .setScmAccounts(asList("ma2")), u -> { - }, otherUser); - - assertThat(es.getIds(UserIndexDefinition.TYPE_USER)).containsExactlyInAnyOrder(user.getUuid(), otherUser.getUuid()); - } - - @Test - public void fail_to_set_null_password_when_local_user() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")); - createDefaultGroup(); - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Password can't be empty"); - - underTest.updateAndCommit(session, user, new UpdateUser().setPassword(null), u -> { - }); - } - - @Test - public void fail_to_update_password_when_user_is_not_local() { - UserDto user = db.users().insertUser(newUserDto() - .setLogin(DEFAULT_LOGIN) - .setLocal(false)); - createDefaultGroup(); - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Password cannot be changed when external authentication is used"); - - underTest.updateAndCommit(session, user, new UpdateUser().setPassword("password2"), u -> { - }); - } - - @Test - public void not_associate_default_group_when_updating_user() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")); - GroupDto defaultGroup = createDefaultGroup(); - - // Existing user, he has no group, and should not be associated to the default one - underTest.updateAndCommit(session, user, new UpdateUser() - .setName("Marius2") - .setEmail("marius2@mail.com") - .setPassword("password2") - .setScmAccounts(asList("ma2")), u -> { - }); - - Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList(DEFAULT_LOGIN)); - assertThat(groups.get(DEFAULT_LOGIN).stream().anyMatch(g -> g.equals(defaultGroup.getName()))).isFalse(); - } - - @Test - public void not_associate_default_group_when_updating_user_if_already_existing() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")); - GroupDto defaultGroup = createDefaultGroup(); - db.users().insertMember(defaultGroup, user); - - // User is already associate to the default group - Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList(DEFAULT_LOGIN)); - assertThat(groups.get(DEFAULT_LOGIN).stream().anyMatch(g -> g.equals(defaultGroup.getName()))).as("Current user groups : %s", groups.get(defaultGroup.getName())).isTrue(); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setName("Marius2") - .setEmail("marius2@mail.com") - .setPassword("password2") - .setScmAccounts(asList("ma2")), u -> { - }); - - // Nothing as changed - groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList(DEFAULT_LOGIN)); - assertThat(groups.get(DEFAULT_LOGIN).stream().anyMatch(g -> g.equals(defaultGroup.getName()))).isTrue(); - } - - @Test - public void fail_to_update_user_when_scm_account_is_already_used() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com").setScmAccounts(singletonList("ma"))); - db.users().insertUser(newLocalUser("john", "John", "john@email.com").setScmAccounts(singletonList("jo"))); - createDefaultGroup(); - - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("The scm account 'jo' is already used by user(s) : 'John (john)'"); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setName("Marius2") - .setEmail("marius2@mail.com") - .setPassword("password2") - .setScmAccounts(asList("jo")), u -> { - }); - } - - @Test - public void fail_to_update_user_when_scm_account_is_user_login() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")); - createDefaultGroup(); - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Login and email are automatically considered as SCM accounts"); - - underTest.updateAndCommit(session, user, new UpdateUser().setScmAccounts(asList(DEFAULT_LOGIN)), u -> { - }); - } - - @Test - public void fail_to_update_user_when_scm_account_is_existing_user_email() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")); - createDefaultGroup(); - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Login and email are automatically considered as SCM accounts"); - - underTest.updateAndCommit(session, user, new UpdateUser().setScmAccounts(asList("marius@lesbronzes.fr")), u -> { - }); - } - - @Test - public void fail_to_update_user_when_scm_account_is_new_user_email() { - UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")); - createDefaultGroup(); - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Login and email are automatically considered as SCM accounts"); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setEmail("marius@newmail.com") - .setScmAccounts(asList("marius@newmail.com")), u -> { - }); - } - - @Test - public void fail_to_update_login_when_format_is_invalid() { - UserDto user = db.users().insertUser(); - createDefaultGroup(); - - expectedException.expect(BadRequestException.class); - expectedException.expectMessage("Use only letters, numbers, and .-_@ please."); - - underTest.updateAndCommit(session, user, new UpdateUser().setLogin("With space"), u -> { - }); - } - - @Test - public void fail_to_update_user_when_login_already_exists() { - createDefaultGroup(); - UserDto user = db.users().insertUser(u -> u.setActive(false)); - UserDto existingUser = db.users().insertUser(u -> u.setLogin("existing_login")); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A user with login 'existing_login' already exists"); - - underTest.updateAndCommit(session, user, new UpdateUser().setLogin(existingUser.getLogin()), u -> { - }); - } - - @Test - public void fail_to_update_user_when_external_id_and_external_provider_already_exists() { - createDefaultGroup(); - UserDto user = db.users().insertUser(u -> u.setActive(false)); - UserDto existingUser = db.users().insertUser(u -> u.setExternalId("existing_external_id").setExternalIdentityProvider("existing_external_provider")); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("A user with provider id 'existing_external_id' and identity provider 'existing_external_provider' already exists"); - - underTest.updateAndCommit(session, user, new UpdateUser() - .setExternalIdentity(new ExternalIdentity(existingUser.getExternalIdentityProvider(), existingUser.getExternalLogin(), existingUser.getExternalId())), u -> { - }); - } - - private GroupDto createDefaultGroup() { - return db.users().insertDefaultGroup(db.getDefaultOrganization()); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/usergroups/DefaultGroupCreatorImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/usergroups/DefaultGroupCreatorImplTest.java deleted file mode 100644 index 01150aec14d..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/usergroups/DefaultGroupCreatorImplTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usergroups; - -import java.util.Optional; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.db.DbTester; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.permission.template.PermissionTemplateDto; -import org.sonar.db.user.GroupDto; - -import static org.assertj.core.api.Assertions.assertThat; - -public class DefaultGroupCreatorImplTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Rule - public DbTester db = DbTester.create(); - - private DefaultGroupCreator underTest = new DefaultGroupCreatorImpl(db.getDbClient()); - - @Test - public void create_default_group() { - OrganizationDto organizationDto = db.organizations().insert(); - - underTest.create(db.getSession(), organizationDto.getUuid()); - - Optional<Integer> defaultGroupId = db.getDbClient().organizationDao().getDefaultGroupId(db.getSession(), organizationDto.getUuid()); - assertThat(defaultGroupId).isPresent(); - assertThat(db.getDbClient().groupDao().selectById(db.getSession(), defaultGroupId.get())) - .extracting(GroupDto::getName, GroupDto::getDescription) - .containsOnly("Members", "All members of the organization"); - } - - @Test - public void fail_with_IAE_when_default_group_already_exist() { - OrganizationDto organizationDto = db.organizations().insert(); - PermissionTemplateDto permissionTemplate = db.permissionTemplates().insertTemplate(); - db.organizations().setDefaultTemplates(organizationDto, permissionTemplate.getUuid(), null, null); - db.users().insertGroup(organizationDto, "Members"); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage(String.format("The group '%s' already exist on organization '%s'", "Members", organizationDto.getUuid())); - - underTest.create(db.getSession(), organizationDto.getUuid()); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/usergroups/DefaultGroupFinderTest.java b/server/sonar-server/src/test/java/org/sonar/server/usergroups/DefaultGroupFinderTest.java deleted file mode 100644 index a894287b77c..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/usergroups/DefaultGroupFinderTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usergroups; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.db.DbTester; -import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.user.GroupDto; - -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; - -public class DefaultGroupFinderTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Rule - public DbTester db = DbTester.create(); - - private DefaultGroupFinder underTest = new DefaultGroupFinder(db.getDbClient()); - - @Test - public void find_default_group() { - OrganizationDto organization = db.organizations().insert(); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "default"); - - GroupDto result = underTest.findDefaultGroup(db.getSession(), organization.getUuid()); - - assertThat(result.getId()).isEqualTo(defaultGroup.getId()); - assertThat(result.getName()).isEqualTo("default"); - } - - @Test - public void fail_with_ISE_when_no_default_group_on_org() { - OrganizationDto organization = db.organizations().insert(); - db.users().insertGroup(organization); - - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage(format("Default group cannot be found on organization '%s'", organization.getUuid())); - - underTest.findDefaultGroup(db.getSession(), organization.getUuid()); - } - - @Test - public void fail_with_NPE_when_default_group_does_not_exist() { - OrganizationDto organization = db.organizations().insert(); - GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "default"); - db.getDbClient().groupDao().deleteById(db.getSession(), defaultGroup.getId()); - - expectedException.expect(NullPointerException.class); - expectedException.expectMessage(format("Group '%s' cannot be found", defaultGroup.getId())); - - underTest.findDefaultGroup(db.getSession(), organization.getUuid()); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/TokenGeneratorImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/TokenGeneratorImplTest.java deleted file mode 100644 index d5222a9e55c..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/TokenGeneratorImplTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usertoken; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TokenGeneratorImplTest { - TokenGeneratorImpl underTest = new TokenGeneratorImpl(); - - @Test - public void generate_different_tokens() { - // this test is not enough to ensure that generated strings are unique, - // but it still does a simple and stupid verification - String firstToken = underTest.generate(); - String secondToken = underTest.generate(); - - assertThat(firstToken) - .isNotEqualTo(secondToken) - .hasSize(40); - } - - @Test - public void token_does_not_contain_colon() { - assertThat(underTest.generate()).doesNotContain(":"); - } - - @Test - public void hash_token() { - String hash = underTest.hash("1234567890123456789012345678901234567890"); - - assertThat(hash) - .hasSize(96) - .isEqualTo("b2501fc3833ae6feba7dc8a973a22d709b7c796ee97cbf66db2c22df873a9fa147b1b630878f771457b7769efd9ffa0d") - .matches("[0-9a-f]+"); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java deleted file mode 100644 index b93ae8bb6c8..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usertoken; - -import java.util.Optional; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.utils.System2; -import org.sonar.db.DbTester; -import org.sonar.db.user.UserDto; -import org.sonar.db.user.UserTokenDto; -import org.sonar.server.authentication.UserLastConnectionDatesUpdater; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class UserTokenAuthenticationTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Rule - public DbTester db = DbTester.create(System2.INSTANCE); - - private TokenGenerator tokenGenerator = mock(TokenGenerator.class); - private UserLastConnectionDatesUpdater userLastConnectionDatesUpdater = mock(UserLastConnectionDatesUpdater.class); - - private UserTokenAuthentication underTest = new UserTokenAuthentication(tokenGenerator, db.getDbClient(), userLastConnectionDatesUpdater); - - @Test - public void return_login_when_token_hash_found_in_db() { - String token = "known-token"; - String tokenHash = "123456789"; - when(tokenGenerator.hash(token)).thenReturn(tokenHash); - UserDto user1 = db.users().insertUser(); - db.users().insertToken(user1, t -> t.setTokenHash(tokenHash)); - UserDto user2 = db.users().insertUser(); - db.users().insertToken(user2, t -> t.setTokenHash("another-token-hash")); - - Optional<String> login = underTest.authenticate(token); - - assertThat(login.isPresent()).isTrue(); - assertThat(login.get()).isEqualTo(user1.getUuid()); - verify(userLastConnectionDatesUpdater).updateLastConnectionDateIfNeeded(any(UserTokenDto.class)); - } - - @Test - public void return_absent_if_token_hash_is_not_found() { - Optional<String> login = underTest.authenticate("unknown-token"); - - assertThat(login.isPresent()).isFalse(); - verify(userLastConnectionDatesUpdater, never()).updateLastConnectionDateIfNeeded(any(UserTokenDto.class)); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java deleted file mode 100644 index 3d2b3669b0c..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.usertoken; - -import org.junit.Test; -import org.sonar.core.platform.ComponentContainer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER; - -public class UserTokenModuleTest { - @Test - public void verify_count_of_added_components() { - ComponentContainer container = new ComponentContainer(); - new UserTokenModule().configure(container); - assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 2); - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/util/MetricKeyValidatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/util/MetricKeyValidatorTest.java deleted file mode 100644 index 68d3eaba609..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/util/MetricKeyValidatorTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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.util; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class MetricKeyValidatorTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Test - public void isMetricKeyValid() { - assertThat(MetricKeyValidator.isMetricKeyValid("")).isFalse(); - assertThat(MetricKeyValidator.isMetricKeyValid("1_2_3-ABC-1_2_3")).isTrue(); - assertThat(MetricKeyValidator.isMetricKeyValid("123_321")).isTrue(); - assertThat(MetricKeyValidator.isMetricKeyValid("123456")).isFalse(); - assertThat(MetricKeyValidator.isMetricKeyValid("1.2.3_A_3:2:1")).isFalse(); - } - - @Test - public void checkMetricKeyFormat() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Malformed metric key '123456'. Allowed characters are alphanumeric, '-', '_', with at least one non-digit."); - - MetricKeyValidator.checkMetricKeyFormat("123456"); - } -} |