]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8761 add UserSession#checkIsSystemAdministrator()
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 10 Feb 2017 14:09:13 +0000 (15:09 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 10 Feb 2017 22:07:21 +0000 (23:07 +0100)
22 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/user/CeUserSession.java
server/sonar-server/src/main/java/org/sonar/server/authentication/BaseContextFactory.java
server/sonar-server/src/main/java/org/sonar/server/authentication/OAuth2ContextFactory.java
server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java
server/sonar-server/src/main/java/org/sonar/server/authentication/ws/LoginAction.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/user/AbstractUserSession.java
server/sonar-server/src/main/java/org/sonar/server/user/DoPrivileged.java
server/sonar-server/src/main/java/org/sonar/server/user/ServerUserSession.java
server/sonar-server/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java
server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactory.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/authentication/BaseContextFactoryTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2ContextFactoryTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/ws/LoginActionTest.java
server/sonar-server/src/test/java/org/sonar/server/tester/AbstractMockUserSession.java
server/sonar-server/src/test/java/org/sonar/server/tester/UserSessionRule.java
server/sonar-server/src/test/java/org/sonar/server/user/DoPrivilegedTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/TestUserSessionFactory.java [new file with mode: 0644]

index 5bcf08d76a0c7420b9460fefce7272fd7a539a3a..bf1d6302fa1fc0eea1ed0f2f54ad72edfb432e2a 100644 (file)
@@ -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();
index 59e9578f47f16c0a22d4ec6d7f620773595b5b35..a608e74ca34018d4c86272bbd40c82015658abe4 100644 (file)
@@ -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));
     }
   }
 }
index fa3d0897e8f549f361fbaf0eb5eeb40b658eda5d..c48cbfe96342512bcdbccb0e649e501741c6d4ea 100644 (file)
@@ -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));
     }
   }
 }
index 13d18758fa8535e0e238118a17c5328a56896a9e..e86e6c80c65ff65e9aa7b22e1d8d62f2709008a2 100644 (file)
@@ -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<UserDto> 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, "-");
     }
   }
index 134242c3d7ff3c1483b3f571134e46eda36d270a..678b90360cea073d2e20f0b1d36796dfbcf497b0 100644 (file)
@@ -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);
index ed03c036ad37250c6e395e7369f75ae1a5e3b589..2e09cabfa1dccaef4bb42b6a36d5bbeccf55e87d 100644 (file)
@@ -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,
index ceed194d26b5439e078643526b57be8a5b7168dd..a402261ec26ba6a20b9d6b99c0fb1fc724ce2c55 100644 (file)
@@ -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;
+  }
 }
index dfed5a8ee7630c37556374523ef14faad530e37d..f485c6e95f9f8af81528eb7a1d60f14ac0522d18 100644 (file)
@@ -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() {
index 8b8224fa08456b71c167744ff77c441b61d72a38..e25aaf8f11e00d620de79b5c44b9c1fbe138c112 100644 (file)
@@ -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<List<GroupDto>> groups;
+  private final OrganizationFlags organizationFlags;
+  private final DefaultOrganizationProvider defaultOrganizationProvider;
+  private final Supplier<List<GroupDto>> groups = Suppliers.memoize(this::loadGroups);
+  private final Supplier<Boolean> isSystemAdministratorSupplier = Suppliers.memoize(this::loadIsSystemAdministrator);
   private SetMultimap<String, String> projectUuidByPermission = HashMultimap.create();
   private SetMultimap<String, String> permissionsByOrganizationUuid;
   private Map<String, String> projectUuidByComponentUuid = newHashMap();
   private List<String> 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<GroupDto> 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;
+    }
+  }
 }
index f23ebb1dcf172686b7d5440727a3e2e95ff8e1bf..05dfd16c2acb0675aa4848b1fbb8d37a7a1a85c7 100644 (file)
@@ -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);
index 009fee22a684b28c0dc964f635d8bfce80118a0b..ff44ba1a54eb41523641159f0224c0e2cf2d5749 100644 (file)
@@ -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:
+   * <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();
+
 }
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 (file)
index 0000000..1cd4582
--- /dev/null
@@ -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 (file)
index 0000000..27107a2
--- /dev/null
@@ -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);
+  }
+}
index 8f6e1db6c5fe87f81525599d684d7ceb43bd991a..15b492d3ef12ae3b86a786f07b89b7bd5ccf7562 100644 (file)
@@ -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 {
index 3e4d4bdbce59e7d1cd632b3dc9a082cd8d83eacf..161d52bd9eae04775e9de3dbfb59140fb85f7206 100644 (file)
@@ -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 {
index 4e650a96b8f49c556e4d595198cdb1673cfe7127..8a669ce9779adb068f8a468c1f7241a50090ba0d 100644 (file)
@@ -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 {
index 6078b7e5df4282c3a345e1f657905a01516d408e..15e33a0ec0153377e54460effb3d235c746ddbda 100644 (file)
@@ -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 {
index 641319a053e139e6a432c0d036bf9d5735fc48d7..b38481f22ca8133a778262d37862218f5d896fff 100644 (file)
@@ -34,6 +34,7 @@ public abstract class AbstractMockUserSession<T extends AbstractMockUserSession>
   private HashMultimap<String, String> permissionsByOrganizationUuid = HashMultimap.create();
   private Map<String, String> projectUuidByComponentUuid = newHashMap();
   private List<String> projectPermissionsCheckedByUuid = newArrayList();
+  private boolean systemAdministrator = false;
 
   protected AbstractMockUserSession(Class<T> clazz) {
     this.clazz = clazz;
@@ -73,4 +74,14 @@ public abstract class AbstractMockUserSession<T extends 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;
+  }
 }
index f0d224ea1aab48fa00ff47c29f4630a79ec15338..6f9d0c55bd7b602d385851cef003f087e24808bf 100644 (file)
@@ -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);
index 526730450df2cbfeb8cb98cb16e29c6ff5b41636..52ca08092b17cfa26a9050eecac004186a02033d 100644 (file)
@@ -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);
index 3e61b16544472baf7cc9f5b27eb7f2a22f51bdab..8ea0020235ad2e0efe84e279dfc19f4fbd37f799 100644 (file)
@@ -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 (file)
index 0000000..86f5235
--- /dev/null
@@ -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<GroupDto> 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<String> 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");
+    }
+  }
+}