From bdd26d9d931ae4250dde12fd1952837acb6d8180 Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Fri, 10 Feb 2017 15:09:13 +0100 Subject: [PATCH] SONAR-8761 add UserSession#checkIsSystemAdministrator() --- .../java/org/sonar/ce/user/CeUserSession.java | 10 ++ .../authentication/BaseContextFactory.java | 13 +- .../authentication/OAuth2ContextFactory.java | 13 +- .../UserSessionInitializer.java | 19 ++- .../server/authentication/ws/LoginAction.java | 13 +- .../platformlevel/PlatformLevel4.java | 2 + .../server/user/AbstractUserSession.java | 8 ++ .../org/sonar/server/user/DoPrivileged.java | 5 + .../sonar/server/user/ServerUserSession.java | 44 ++++--- .../server/user/ThreadLocalUserSession.java | 11 ++ .../org/sonar/server/user/UserSession.java | 19 +++ .../sonar/server/user/UserSessionFactory.java | 32 +++++ .../server/user/UserSessionFactoryImpl.java | 54 ++++++++ .../BaseContextFactoryTest.java | 4 +- .../OAuth2ContextFactoryTest.java | 4 +- .../UserSessionInitializerTest.java | 7 +- .../authentication/ws/LoginActionTest.java | 5 +- .../tester/AbstractMockUserSession.java | 11 ++ .../sonar/server/tester/UserSessionRule.java | 11 ++ .../sonar/server/user/DoPrivilegedTest.java | 1 + .../server/user/ServerUserSessionTest.java | 105 ++++++++++++++-- .../server/user/TestUserSessionFactory.java | 117 ++++++++++++++++++ 22 files changed, 444 insertions(+), 64 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactory.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/user/TestUserSessionFactory.java diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/user/CeUserSession.java b/server/sonar-ce/src/main/java/org/sonar/ce/user/CeUserSession.java index 5bcf08d76a0..bf1d6302fa1 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/user/CeUserSession.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/user/CeUserSession.java @@ -93,6 +93,16 @@ public class CeUserSession implements UserSession { throw notImplemented(); } + @Override + public boolean isSystemAdministrator() { + throw notImplemented(); + } + + @Override + public UserSession checkIsSystemAdministrator() { + throw notImplemented(); + } + @Override public boolean hasComponentPermission(String permission, ComponentDto component) { throw notImplemented(); 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 index 59e9578f47f..a608e74ca34 100644 --- 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 @@ -24,24 +24,23 @@ 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.DbClient; import org.sonar.db.user.UserDto; -import org.sonar.server.user.ServerUserSession; import org.sonar.server.user.ThreadLocalUserSession; +import org.sonar.server.user.UserSessionFactory; import static org.sonar.server.authentication.event.AuthenticationEvent.Source; public class BaseContextFactory { - private final DbClient dbClient; private final ThreadLocalUserSession threadLocalUserSession; private final UserIdentityAuthenticator userIdentityAuthenticator; private final Server server; private final JwtHttpHandler jwtHttpHandler; + private final UserSessionFactory userSessionFactory; - public BaseContextFactory(DbClient dbClient, UserIdentityAuthenticator userIdentityAuthenticator, Server server, JwtHttpHandler jwtHttpHandler, - ThreadLocalUserSession threadLocalUserSession) { - this.dbClient = dbClient; + public BaseContextFactory(UserIdentityAuthenticator userIdentityAuthenticator, Server server, JwtHttpHandler jwtHttpHandler, + ThreadLocalUserSession threadLocalUserSession, UserSessionFactory userSessionFactory) { + this.userSessionFactory = userSessionFactory; this.userIdentityAuthenticator = userIdentityAuthenticator; this.server = server; this.jwtHttpHandler = jwtHttpHandler; @@ -82,7 +81,7 @@ public class BaseContextFactory { public void authenticate(UserIdentity userIdentity) { UserDto userDto = userIdentityAuthenticator.authenticate(userIdentity, identityProvider, Source.external(identityProvider)); jwtHttpHandler.generateToken(userDto, request, response); - threadLocalUserSession.set(ServerUserSession.createForUser(dbClient, userDto)); + threadLocalUserSession.set(userSessionFactory.create(userDto)); } } } 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 index fa3d0897e8f..c48cbfe9634 100644 --- 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 @@ -26,11 +26,10 @@ import org.sonar.api.platform.Server; import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.UserIdentity; import org.sonar.api.utils.MessageException; -import org.sonar.db.DbClient; import org.sonar.db.user.UserDto; import org.sonar.server.authentication.event.AuthenticationEvent; -import org.sonar.server.user.ServerUserSession; import org.sonar.server.user.ThreadLocalUserSession; +import org.sonar.server.user.UserSessionFactory; import static java.lang.String.format; import static org.sonar.api.CoreProperties.SERVER_BASE_URL; @@ -38,21 +37,21 @@ import static org.sonar.server.authentication.OAuth2CallbackFilter.CALLBACK_PATH public class OAuth2ContextFactory { - private final DbClient dbClient; private final ThreadLocalUserSession threadLocalUserSession; private final UserIdentityAuthenticator userIdentityAuthenticator; private final Server server; private final OAuthCsrfVerifier csrfVerifier; private final JwtHttpHandler jwtHttpHandler; + private final UserSessionFactory userSessionFactory; - public OAuth2ContextFactory(DbClient dbClient, ThreadLocalUserSession threadLocalUserSession, UserIdentityAuthenticator userIdentityAuthenticator, Server server, - OAuthCsrfVerifier csrfVerifier, JwtHttpHandler jwtHttpHandler) { - this.dbClient = dbClient; + public OAuth2ContextFactory(ThreadLocalUserSession threadLocalUserSession, UserIdentityAuthenticator userIdentityAuthenticator, Server server, + OAuthCsrfVerifier csrfVerifier, JwtHttpHandler jwtHttpHandler, UserSessionFactory userSessionFactory) { this.threadLocalUserSession = threadLocalUserSession; this.userIdentityAuthenticator = userIdentityAuthenticator; this.server = server; this.csrfVerifier = csrfVerifier; this.jwtHttpHandler = jwtHttpHandler; + this.userSessionFactory = userSessionFactory; } public OAuth2IdentityProvider.InitContext newContext(HttpServletRequest request, HttpServletResponse response, OAuth2IdentityProvider identityProvider) { @@ -126,7 +125,7 @@ public class OAuth2ContextFactory { public void authenticate(UserIdentity userIdentity) { UserDto userDto = userIdentityAuthenticator.authenticate(userIdentity, identityProvider, AuthenticationEvent.Source.oauth2(identityProvider)); jwtHttpHandler.generateToken(userDto, request, response); - threadLocalUserSession.set(ServerUserSession.createForUser(dbClient, userDto)); + threadLocalUserSession.set(userSessionFactory.create(userDto)); } } } 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 index 13d18758fa8..e86e6c80c65 100644 --- 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 @@ -26,12 +26,12 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.sonar.api.config.Settings; import org.sonar.api.server.ServerSide; -import org.sonar.db.DbClient; import org.sonar.db.user.UserDto; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationException; -import org.sonar.server.user.ServerUserSession; import org.sonar.server.user.ThreadLocalUserSession; +import org.sonar.server.user.UserSession; +import org.sonar.server.user.UserSessionFactory; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static org.sonar.api.CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY; @@ -43,8 +43,6 @@ import static org.sonar.server.authentication.event.AuthenticationEvent.Source; import static org.sonar.server.authentication.ws.LoginAction.LOGIN_URL; import static org.sonar.server.authentication.ws.LogoutAction.LOGOUT_URL; import static org.sonar.server.authentication.ws.ValidateAction.VALIDATE_URL; -import static org.sonar.server.user.ServerUserSession.createForAnonymous; -import static org.sonar.server.user.ServerUserSession.createForUser; @ServerSide public class UserSessionInitializer { @@ -72,23 +70,24 @@ public class UserSessionInitializer { .excludes(SKIPPED_URLS) .build(); - private final DbClient dbClient; private final Settings settings; private final JwtHttpHandler jwtHttpHandler; private final BasicAuthenticator basicAuthenticator; private final SsoAuthenticator ssoAuthenticator; private final ThreadLocalUserSession threadLocalSession; private final AuthenticationEvent authenticationEvent; + private final UserSessionFactory userSessionFactory; - public UserSessionInitializer(DbClient dbClient, Settings settings, JwtHttpHandler jwtHttpHandler, BasicAuthenticator basicAuthenticator, - SsoAuthenticator ssoAuthenticator, ThreadLocalUserSession threadLocalSession, AuthenticationEvent authenticationEvent) { - this.dbClient = dbClient; + public UserSessionInitializer(Settings settings, JwtHttpHandler jwtHttpHandler, BasicAuthenticator basicAuthenticator, + SsoAuthenticator ssoAuthenticator, ThreadLocalUserSession threadLocalSession, AuthenticationEvent authenticationEvent, + UserSessionFactory userSessionFactory) { this.settings = settings; this.jwtHttpHandler = jwtHttpHandler; this.basicAuthenticator = basicAuthenticator; this.ssoAuthenticator = ssoAuthenticator; this.threadLocalSession = threadLocalSession; this.authenticationEvent = authenticationEvent; + this.userSessionFactory = userSessionFactory; } public boolean initUserSession(HttpServletRequest request, HttpServletResponse response) { @@ -124,7 +123,7 @@ public class UserSessionInitializer { private void setUserSession(HttpServletRequest request, HttpServletResponse response) { Optional user = authenticate(request, response); if (user.isPresent()) { - ServerUserSession session = createForUser(dbClient, user.get()); + UserSession session = userSessionFactory.create(user.get()); threadLocalSession.set(session); request.setAttribute(ACCESS_LOG_LOGIN, session.getLogin()); } else { @@ -134,7 +133,7 @@ public class UserSessionInitializer { .setMessage("User must be authenticated") .build(); } - threadLocalSession.set(createForAnonymous(dbClient)); + threadLocalSession.set(userSessionFactory.createAnonymous()); request.setAttribute(ACCESS_LOG_LOGIN, "-"); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/ws/LoginAction.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/ws/LoginAction.java index 134242c3d7f..678b90360ce 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/ws/LoginAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/ws/LoginAction.java @@ -30,15 +30,14 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.sonar.api.server.ws.WebService; import org.sonar.api.web.ServletFilter; -import org.sonar.db.DbClient; import org.sonar.db.user.UserDto; import org.sonar.server.authentication.CredentialsAuthenticator; import org.sonar.server.authentication.JwtHttpHandler; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationException; import org.sonar.server.exceptions.UnauthorizedException; -import org.sonar.server.user.ServerUserSession; import org.sonar.server.user.ThreadLocalUserSession; +import org.sonar.server.user.UserSessionFactory; import org.sonar.server.ws.ServletFilterHandler; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; @@ -54,19 +53,19 @@ public class LoginAction extends ServletFilter implements AuthenticationWsAction private static final String LOGIN_ACTION = "login"; public static final String LOGIN_URL = "/" + AUTHENTICATION_CONTROLLER + "/" + LOGIN_ACTION; - private final DbClient dbClient; private final CredentialsAuthenticator credentialsAuthenticator; private final JwtHttpHandler jwtHttpHandler; private final ThreadLocalUserSession threadLocalUserSession; private final AuthenticationEvent authenticationEvent; + private final UserSessionFactory userSessionFactory; - public LoginAction(DbClient dbClient, CredentialsAuthenticator credentialsAuthenticator, JwtHttpHandler jwtHttpHandler, - ThreadLocalUserSession threadLocalUserSession, AuthenticationEvent authenticationEvent) { - this.dbClient = dbClient; + public LoginAction(CredentialsAuthenticator credentialsAuthenticator, JwtHttpHandler jwtHttpHandler, + ThreadLocalUserSession threadLocalUserSession, AuthenticationEvent authenticationEvent, UserSessionFactory userSessionFactory) { this.credentialsAuthenticator = credentialsAuthenticator; this.jwtHttpHandler = jwtHttpHandler; this.threadLocalUserSession = threadLocalUserSession; this.authenticationEvent = authenticationEvent; + this.userSessionFactory = userSessionFactory; } @Override @@ -104,7 +103,7 @@ public class LoginAction extends ServletFilter implements AuthenticationWsAction try { UserDto userDto = authenticate(request, login, password); jwtHttpHandler.generateToken(userDto, request, response); - threadLocalUserSession.set(ServerUserSession.createForUser(dbClient, userDto)); + threadLocalUserSession.set(userSessionFactory.create(userDto)); } catch (AuthenticationException e) { authenticationEvent.loginFailure(request, e); response.setStatus(HTTP_UNAUTHORIZED); diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index ed03c036ad3..2e09cabfa1d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -198,6 +198,7 @@ import org.sonar.server.user.DefaultUserFinder; import org.sonar.server.user.DeprecatedUserFinder; import org.sonar.server.user.NewUserNotifier; import org.sonar.server.user.SecurityRealmFactory; +import org.sonar.server.user.UserSessionFactoryImpl; import org.sonar.server.user.UserUpdater; import org.sonar.server.user.index.UserIndex; import org.sonar.server.user.index.UserIndexDefinition; @@ -334,6 +335,7 @@ public class PlatformLevel4 extends PlatformLevel { AuthenticationModule.class, // users + UserSessionFactoryImpl.class, SecurityRealmFactory.class, DeprecatedUserFinder.class, NewUserNotifier.class, 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 index ceed194d26b..a402261ec26 100644 --- 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 @@ -99,4 +99,12 @@ public abstract class AbstractUserSession implements UserSession { public static ForbiddenException insufficientPrivilegesException() { return INSUFFICIENT_PRIVILEGES_EXCEPTION; } + + @Override + public final UserSession checkIsSystemAdministrator() { + if (!isSystemAdministrator()) { + throw insufficientPrivilegesException(); + } + return this; + } } 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 index dfed5a8ee76..f485c6e95f9 100644 --- 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 @@ -111,6 +111,11 @@ public final class DoPrivileged { protected boolean hasProjectUuidPermission(String permission, String projectUuid) { return true; } + + @Override + public boolean isSystemAdministrator() { + return true; + } } private void start() { 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 index 8b8224fa084..e25aaf8f11e 100644 --- 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 @@ -32,14 +32,16 @@ import java.util.Optional; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import org.sonar.core.permission.GlobalPermissions; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; 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 com.google.common.collect.Maps.newHashMap; -import static java.util.Objects.requireNonNull; /** * Implementation of {@link UserSession} used in web server @@ -48,25 +50,21 @@ public class ServerUserSession extends AbstractUserSession { @CheckForNull private final UserDto userDto; private final DbClient dbClient; - private final Supplier> groups; + private final OrganizationFlags organizationFlags; + private final DefaultOrganizationProvider defaultOrganizationProvider; + private final Supplier> groups = Suppliers.memoize(this::loadGroups); + private final Supplier isSystemAdministratorSupplier = Suppliers.memoize(this::loadIsSystemAdministrator); private SetMultimap projectUuidByPermission = HashMultimap.create(); private SetMultimap permissionsByOrganizationUuid; private Map projectUuidByComponentUuid = newHashMap(); private List projectPermissionsCheckedByUuid = new ArrayList<>(); - private ServerUserSession(DbClient dbClient, @Nullable UserDto userDto) { - this.userDto = userDto; + ServerUserSession(DbClient dbClient, OrganizationFlags organizationFlags, + DefaultOrganizationProvider defaultOrganizationProvider, @Nullable UserDto userDto) { this.dbClient = dbClient; - this.groups = Suppliers.memoize(this::loadGroups); - } - - public static ServerUserSession createForUser(DbClient dbClient, UserDto userDto) { - requireNonNull(userDto, "UserDto must not be null"); - return new ServerUserSession(dbClient, userDto); - } - - public static ServerUserSession createForAnonymous(DbClient dbClient) { - return new ServerUserSession(dbClient, null); + this.organizationFlags = organizationFlags; + this.defaultOrganizationProvider = defaultOrganizationProvider; + this.userDto = userDto; } private List loadGroups() { @@ -171,4 +169,22 @@ public class ServerUserSession extends AbstractUserSession { projectPermissionsCheckedByUuid.add(permission); } + @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 hasOrganizationPermission(uuidOfDefaultOrg, GlobalPermissions.SYSTEM_ADMIN); + } + // organization feature is enabled -> requires to be root + return false; + } + } } 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 index f23ebb1dcf1..05dfd16c2ac 100644 --- 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 @@ -109,6 +109,17 @@ public class ThreadLocalUserSession implements UserSession { 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); 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 index 009fee22a68..ff44ba1a54e 100644 --- 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 @@ -128,4 +128,23 @@ public interface UserSession { * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}. */ UserSession checkComponentUuidPermission(String permission, String componentUuid); + + /** + * Whether user can administrate system, for example to use cross-organizations services + * like update center, system info or management of users. + * + * Returns {@code true} if: + *
    + *
  • {@link #isRoot()} is {@code true}
  • + *
  • organization feature is disabled and user is administrator of the (single) default organization
  • + *
+ */ + boolean isSystemAdministrator(); + + /** + * Ensures that {@link #isSystemAdministrator()} is {@code true}, + * otherwise throws {@link org.sonar.server.exceptions.ForbiddenException}. + */ + UserSession checkIsSystemAdministrator(); + } 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 new file mode 100644 index 00000000000..1cd4582d733 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactory.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.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 new file mode 100644 index 00000000000..27107a2cfb8 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.user; + +import org.sonar.api.server.ServerSide; +import org.sonar.db.DbClient; +import org.sonar.db.user.UserDto; +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; + + public UserSessionFactoryImpl(DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider, + OrganizationFlags organizationFlags) { + this.dbClient = dbClient; + this.defaultOrganizationProvider = defaultOrganizationProvider; + this.organizationFlags = organizationFlags; + } + + @Override + public ServerUserSession create(UserDto user) { + requireNonNull(user, "UserDto must not be null"); + 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/test/java/org/sonar/server/authentication/BaseContextFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/BaseContextFactoryTest.java index 8f6e1db6c5f..15b492d3ef1 100644 --- 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 @@ -34,6 +34,7 @@ import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.user.UserDto; import org.sonar.server.authentication.event.AuthenticationEvent; +import org.sonar.server.user.TestUserSessionFactory; import org.sonar.server.user.ThreadLocalUserSession; import org.sonar.server.user.UserSession; @@ -72,8 +73,9 @@ public class BaseContextFactoryTest { 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(dbClient, userIdentityAuthenticator, server, jwtHttpHandler, threadLocalUserSession); + private BaseContextFactory underTest = new BaseContextFactory(userIdentityAuthenticator, server, jwtHttpHandler, threadLocalUserSession, userSessionFactory); @Before public void setUp() throws Exception { 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 index 3e4d4bdbce5..161d52bd9ea 100644 --- 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 @@ -35,6 +35,7 @@ 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.user.TestUserSessionFactory; import org.sonar.server.user.ThreadLocalUserSession; import org.sonar.server.user.UserSession; @@ -74,13 +75,14 @@ public class OAuth2ContextFactoryTest { private Server server = mock(Server.class); private OAuthCsrfVerifier csrfVerifier = mock(OAuthCsrfVerifier.class); private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); + private TestUserSessionFactory userSessionFactory = TestUserSessionFactory.standalone(); 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(dbClient, threadLocalUserSession, userIdentityAuthenticator, server, csrfVerifier, jwtHttpHandler); + private OAuth2ContextFactory underTest = new OAuth2ContextFactory(threadLocalUserSession, userIdentityAuthenticator, server, csrfVerifier, jwtHttpHandler, userSessionFactory); @Before public void setUp() throws Exception { 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 index 4e650a96b8f..8a669ce9779 100644 --- 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 @@ -37,6 +37,7 @@ import org.sonar.db.user.UserDto; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationException; import org.sonar.server.user.ServerUserSession; +import org.sonar.server.user.TestUserSessionFactory; import org.sonar.server.user.ThreadLocalUserSession; import org.sonar.server.user.UserSession; @@ -73,13 +74,13 @@ public class UserSessionInitializerTest { private BasicAuthenticator basicAuthenticator = mock(BasicAuthenticator.class); private SsoAuthenticator ssoAuthenticator = mock(SsoAuthenticator.class); private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - + private TestUserSessionFactory userSessionFactory = TestUserSessionFactory.standalone(); private Settings settings = new MapSettings(); private UserDto user = newUserDto(); - private UserSessionInitializer underTest = new UserSessionInitializer(dbClient, settings, jwtHttpHandler, basicAuthenticator, - ssoAuthenticator, userSession, authenticationEvent); + private UserSessionInitializer underTest = new UserSessionInitializer(settings, jwtHttpHandler, basicAuthenticator, + ssoAuthenticator, userSession, authenticationEvent, userSessionFactory); @Before public void setUp() throws Exception { diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/ws/LoginActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/ws/LoginActionTest.java index 6078b7e5df4..15e33a0ec01 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/ws/LoginActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/ws/LoginActionTest.java @@ -38,6 +38,7 @@ import org.sonar.server.authentication.JwtHttpHandler; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationException; import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.user.TestUserSessionFactory; import org.sonar.server.user.ThreadLocalUserSession; import static org.assertj.core.api.Assertions.assertThat; @@ -71,10 +72,10 @@ public class LoginActionTest { private CredentialsAuthenticator credentialsAuthenticator = mock(CredentialsAuthenticator.class); private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); + private TestUserSessionFactory userSessionFactory = TestUserSessionFactory.standalone(); private UserDto user = UserTesting.newUserDto().setLogin(LOGIN); - - private LoginAction underTest = new LoginAction(dbClient, credentialsAuthenticator, jwtHttpHandler, threadLocalUserSession, authenticationEvent); + private LoginAction underTest = new LoginAction(credentialsAuthenticator, jwtHttpHandler, threadLocalUserSession, authenticationEvent, userSessionFactory); @Before public void setUp() throws Exception { 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 index 641319a053e..b38481f22ca 100644 --- 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 @@ -34,6 +34,7 @@ public abstract class AbstractMockUserSession private HashMultimap permissionsByOrganizationUuid = HashMultimap.create(); private Map projectUuidByComponentUuid = newHashMap(); private List projectPermissionsCheckedByUuid = newArrayList(); + private boolean systemAdministrator = false; protected AbstractMockUserSession(Class clazz) { this.clazz = clazz; @@ -73,4 +74,14 @@ public abstract class AbstractMockUserSession permissionsByOrganizationUuid.put(organizationUuid, permission); return clazz.cast(this); } + + public T setSystemAdministrator(boolean b) { + this.systemAdministrator = b; + return clazz.cast(this); + } + + @Override + public boolean isSystemAdministrator() { + return systemAdministrator; + } } 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 index f0d224ea1aa..6f9d0c55bd7 100644 --- 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 @@ -304,6 +304,17 @@ public class UserSessionRule implements TestRule, UserSession { return this; } + @Override + public boolean isSystemAdministrator() { + return currentUserSession.isSystemAdministrator(); + } + + @Override + public UserSession checkIsSystemAdministrator() { + currentUserSession.checkIsSystemAdministrator(); + return this; + } + @Override public UserSession checkOrganizationPermission(String organizationUuid, String permission) { currentUserSession.checkOrganizationPermission(organizationUuid, permission); 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 index 526730450df..52ca08092b1 100644 --- 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 @@ -48,6 +48,7 @@ public class DoPrivilegedTest { // 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(); // verify session in place after task is done assertThat(threadLocalUserSession.get()).isSameAs(session); 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 index 3e61b165444..8ea0020235a 100644 --- 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 @@ -34,13 +34,13 @@ 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 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.user.UserTesting.newUserDto; -import static org.sonar.server.user.ServerUserSession.createForAnonymous; -import static org.sonar.server.user.ServerUserSession.createForUser; public class ServerUserSessionTest { private static final String LOGIN = "marius"; @@ -66,6 +66,8 @@ public class ServerUserSessionTest { private DbClient dbClient = db.getDbClient(); private UserDto userDto = newUserDto().setLogin(LOGIN); + private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone(); + private TestDefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); private OrganizationDto organization; private ComponentDto project; @@ -169,12 +171,6 @@ public class ServerUserSessionTest { session.checkComponentUuidPermission(UserRole.USER, "another-uuid"); } - @Test - public void fail_if_user_dto_is_null() throws Exception { - expectedException.expect(NullPointerException.class); - newUserSession(null); - } - @Test public void anonymous_user() throws Exception { UserSession session = newAnonymousSession(); @@ -210,7 +206,7 @@ public class ServerUserSessionTest { } @Test - public void hasOrganizationPermission_for_logged_in_user() { + public void test_hasOrganizationPermission_for_logged_in_user() { OrganizationDto org = db.organizations().insert(); ComponentDto project = db.components().insertProject(org); db.users().insertPermissionOnUser(org, userDto, PROVISIONING); @@ -233,12 +229,97 @@ public class ServerUserSessionTest { assertThat(session.hasOrganizationPermission("another-org", PROVISIONING)).isFalse(); } - private ServerUserSession newUserSession(UserDto userDto) { - return createForUser(dbClient, userDto); + @Test + public void isSystemAdministrator_returns_true_if_org_feature_is_enabled_and_user_is_root() { + organizationFlags.setEnabled(true); + userDto = db.users().makeRoot(userDto); + UserSession session = newUserSession(userDto); + + assertThat(session.isSystemAdministrator()).isTrue(); + } + + @Test + public void isSystemAdministrator_returns_false_if_org_feature_is_enabled_and_user_is_not_root() { + organizationFlags.setEnabled(true); + userDto = db.users().makeNotRoot(userDto); + UserSession session = newUserSession(userDto); + + 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 = db.users().makeNotRoot(userDto); + db.users().insertPermissionOnUser(db.getDefaultOrganization(), userDto, SYSTEM_ADMIN); + UserSession session = newUserSession(userDto); + + 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 = db.users().makeNotRoot(userDto); + db.users().insertPermissionOnUser(db.getDefaultOrganization(), userDto, SYSTEM_ADMIN); + UserSession session = newUserSession(userDto); + + 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 = db.users().makeNotRoot(userDto); + db.users().insertPermissionOnUser(db.getDefaultOrganization(), userDto, PROVISIONING); + UserSession session = newUserSession(userDto); + + assertThat(session.isSystemAdministrator()).isFalse(); + } + + @Test + public void keep_isSystemAdministrator_flag_in_cache() { + organizationFlags.setEnabled(false); + userDto = db.users().makeNotRoot(userDto); + db.users().insertPermissionOnUser(db.getDefaultOrganization(), userDto, SYSTEM_ADMIN); + UserSession session = newUserSession(userDto); + + session.checkIsSystemAdministrator(); + + db.getDbClient().userDao().deactivateUserByLogin(db.getSession(), userDto.getLogin()); + 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 = db.users().makeRoot(userDto); + UserSession session = newUserSession(userDto); + + session.checkIsSystemAdministrator(); + } + + @Test + public void checkIsSystemAdministrator_throws_ForbiddenException_if_not_system_administrator() { + organizationFlags.setEnabled(true); + userDto = db.users().makeNotRoot(userDto); + UserSession session = newUserSession(userDto); + + expectedException.expect(ForbiddenException.class); + expectedException.expectMessage("Insufficient privileges"); + + session.checkIsSystemAdministrator(); + } + + private ServerUserSession newUserSession(@Nullable UserDto userDto) { + return new ServerUserSession(dbClient, organizationFlags, defaultOrganizationProvider, userDto); } private ServerUserSession newAnonymousSession() { - return createForAnonymous(dbClient); + return newUserSession(null); } private void addProjectPermissions(ComponentDto component, String... permissions) { 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 new file mode 100644 index 00000000000..86f52354045 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/user/TestUserSessionFactory.java @@ -0,0 +1,117 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.user; + +import java.util.Collection; +import java.util.Optional; +import javax.annotation.Nullable; +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 getName() { + return user != null ? user.getName() : null; + } + + @Override + public Integer getUserId() { + return user != null ? user.getId().intValue() : null; + } + + @Override + public Collection getGroups() { + throw notImplemented(); + } + + @Override + public boolean isLoggedIn() { + return user != null; + } + + @Override + public boolean isRoot() { + throw notImplemented(); + } + + @Override + protected boolean hasOrganizationPermissionImpl(String organizationUuid, String permission) { + throw notImplemented(); + } + + @Override + protected Optional componentUuidToProjectUuid(String componentUuid) { + throw notImplemented(); + } + + @Override + protected boolean hasProjectUuidPermission(String permission, String projectUuid) { + throw notImplemented(); + } + + @Override + public boolean isSystemAdministrator() { + throw notImplemented(); + } + + private static RuntimeException notImplemented() { + return new UnsupportedOperationException("not implemented"); + } + } +} -- 2.39.5