]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4475 Add method to check that at least one admin exists
authorJulien Lancelot <julien.lancelot@gmail.com>
Wed, 14 Aug 2013 15:43:52 +0000 (17:43 +0200)
committerJulien Lancelot <julien.lancelot@gmail.com>
Wed, 14 Aug 2013 15:43:52 +0000 (17:43 +0200)
13 files changed:
plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-core/src/main/java/org/sonar/core/user/RoleDao.java
sonar-core/src/main/java/org/sonar/core/user/RoleMapper.java
sonar-core/src/main/resources/org/sonar/core/user/RoleMapper.xml
sonar-core/src/test/java/org/sonar/core/user/RoleDaoTest.java
sonar-core/src/test/resources/org/sonar/core/user/RoleDaoTest/should_count_user_with_permission.xml [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/utils/MessageException.java
sonar-plugin-api/src/test/java/org/sonar/api/utils/MessageExceptionTest.java
sonar-server/src/main/java/org/sonar/server/permission/InternalPermissionService.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/groups_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/controllers/users_controller.rb
sonar-server/src/main/webapp/javascripts/application.js
sonar-server/src/test/java/org/sonar/server/permission/InternalPermissionServiceTest.java

index eb291c817c1ae0c406441a9c04be65960017473d..01df59dce079ff87f871eac22350917d256178c3 100644 (file)
@@ -2233,6 +2233,7 @@ global_permissions.scan.desc=Ability to execute analyses, and to get all setting
 global_permissions.dryRunScan=Execute Local Analysis (Dry Run)
 global_permissions.dryRunScan.desc=Ability to execute local (dry run) analyses without pushing the results to the server, and to get all settings required to perform a local analysis. This permission does not include the ability to access secured settings such as the scm account password, the jira account password, and so on.<br/>\
 This permission is <em>required</em> to execute a local analysis in Eclipse or via the Issues Report plugin.
+global_permissions.error.need_at_lest_one_admin=At least one admin must be defined.
 
 #------------------------------------------------------------------------------
 #
index c8bc741db1b1c216b7677c9eb7bcced6042bc79f..b936f0e33081d472408271216eafda729a3c6040 100644 (file)
@@ -144,4 +144,14 @@ public class RoleDao implements TaskComponent, ServerComponent {
       MyBatis.closeQuietly(session);
     }
   }
+
+  public int countUserWithPermission(String permission) {
+    SqlSession session = mybatis.openSession();
+    try {
+      RoleMapper mapper = session.getMapper(RoleMapper.class);
+      return mapper.countUserWithPermission(permission);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
 }
index 0fcaac7973bfe20ef333baa57bedc7486492fa38..7bd9fb0315e255375ef7fc1eccad592374dba821 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.core.user;
 
 import org.apache.ibatis.annotations.Param;
 
-import javax.annotation.Nullable;
 import java.util.List;
 
 /**
@@ -49,5 +48,6 @@ public interface RoleMapper {
 
   int countUserRoles(Long resourceId);
 
-  List<Long> countSystemAdministrators(@Nullable @Param("groupName") String groupName);
+  int countUserWithPermission(@Param("permission") String permission);
+
 }
index 3dba348d55a23be25e0b89ecff4e5430f0c8d026..bcfbe774bd32de70ada0a571fbc80d34d1bcadb9 100644 (file)
     SELECT count(id)
     FROM group_roles WHERE resource_id=#{id}
   </select>
+
+  <select id="countUserWithPermission" parameterType="map" resultType="Integer">
+    SELECT COUNT(DISTINCT tmp.id)
+    FROM (
+    SELECT u.id as id
+    FROM users AS u
+    INNER JOIN user_roles AS ur ON ur.user_id = u.id
+    <where>
+      AND (ur.role = #{permission} AND ur.resource_id IS NULL)
+      AND u.active = true
+    </where>
+    UNION
+    select u.id as id
+    FROM users as u
+    INNER JOIN groups_users AS gu ON gu.user_id = u.id
+    INNER JOIN group_roles AS gr ON gr.group_id = gu.group_id
+    INNER JOIN groups AS g ON g.id = gu.group_id
+    <where>
+      AND (gr.role = #{permission} AND gr.resource_id IS NULL)
+      AND u.active = true
+    </where>
+    ) AS tmp
+  </select>
+
 </mapper>
index 6d0c9b0830e4d6e1e1d720a99bfe02247041df85..427c2aee456f3b5ef4c7ad244a581ba592fcd34f 100644 (file)
@@ -76,4 +76,19 @@ public class RoleDaoTest extends AbstractDaoTestCase {
     checkTable("groupPermissions", "group_roles", "group_id", "role");
   }
 
+  @Test
+  public void should_count_user_with_permission() {
+    setupData("should_count_user_with_permission");
+
+    RoleDao dao = new RoleDao(getMyBatis());
+
+    // 1 user have role 'admin', 1 user owns to the 'admin' group
+    assertThat(dao.countUserWithPermission("admin")).isEqualTo(2);
+    // 1 user have role 'profileadmin'
+    assertThat(dao.countUserWithPermission("profileadmin")).isEqualTo(1);
+    // 1 user owns to the 'user' group
+    assertThat(dao.countUserWithPermission("user")).isEqualTo(1);
+    assertThat(dao.countUserWithPermission("unknown")).isEqualTo(0);
+  }
+
 }
diff --git a/sonar-core/src/test/resources/org/sonar/core/user/RoleDaoTest/should_count_user_with_permission.xml b/sonar-core/src/test/resources/org/sonar/core/user/RoleDaoTest/should_count_user_with_permission.xml
new file mode 100644 (file)
index 0000000..8fe58e3
--- /dev/null
@@ -0,0 +1,25 @@
+<dataset>
+
+  <!-- users having role -->
+
+  <users id="200" login="admin_user" name="admin_user" active="[true]"/>
+  <users id="201" login="profile_admin_user" name="profile_admin_user" active="[true]"/>
+
+  <user_roles id="1" user_id="200" role="admin"/>
+  <user_roles id="2" user_id="201" role="profileadmin"/>
+
+  <!-- users having group -->
+
+  <users id="202" login="admin_group" name="admin_group" active="[true]"/>
+  <users id="203" login="user_group" name="user_group" active="[true]"/>
+
+  <groups id="100" name="sonar-administrators"/>
+  <groups id="101" name="sonar-users"/>
+
+  <group_roles id="1" group_id="100" role="admin"/>
+  <group_roles id="2" group_id="101" role="user"/>
+
+  <groups_users user_id="202" group_id="100"/>
+  <groups_users user_id="203" group_id="101"/>
+
+</dataset>
index 25290dac9b2bc42bb7c17910e013b5c72a6994c8..7a703557b5a3dcf6b754ce2fe35b6f34f42f12d3 100644 (file)
  */
 package org.sonar.api.utils;
 
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static com.google.common.collect.Lists.newArrayList;
+
 /**
  * Runtime exception for "functional" errors. It aims to be displayed to end-users, without any technical information
  * like stack traces.
@@ -27,10 +35,19 @@ package org.sonar.api.utils;
  */
 public class MessageException extends RuntimeException {
 
+  private String l10nKey;
+  private Collection<Object> l10nParams;
+
   public MessageException(String s) {
     super(s);
   }
 
+  public MessageException(@Nullable String message, @Nullable String l10nKey, @Nullable Object[] l10nParams) {
+    super(message);
+    this.l10nKey = l10nKey;
+    this.l10nParams = l10nParams == null ? Collections.emptyList() : Collections.unmodifiableCollection(newArrayList(l10nParams));
+  }
+
   /**
    * Does not fill in the stack trace
    *
@@ -46,5 +63,14 @@ public class MessageException extends RuntimeException {
     return getMessage();
   }
 
+  @CheckForNull
+  public String l10nKey() {
+    return l10nKey;
+  }
+
+  @CheckForNull
+  public Collection<Object> l10nParams() {
+    return l10nParams;
+  }
 
 }
index 0b734416e947b8b9822d7439275785b1313ea2ef..f036069412fe7b7a8e68fe8495f0a8793c046a77 100644 (file)
@@ -47,4 +47,18 @@ public class MessageExceptionTest {
       assertThat(writer.toString()).isEqualTo(message + System.getProperty("line.separator"));
     }
   }
+
+  @Test
+  public void should_create_exception_with_status_and_l10n_message_with_param(){
+    MessageException exception = new MessageException(null, "key", new String[]{"value"});
+    assertThat(exception.l10nKey()).isEqualTo("key");
+    assertThat(exception.l10nParams()).containsOnly("value");
+  }
+
+  @Test
+  public void should_create_exception_with_status_and_l10n_message_without_param(){
+    MessageException exception = new MessageException(null, "key", null);
+    assertThat(exception.l10nKey()).isEqualTo("key");
+    assertThat(exception.l10nParams()).isEmpty();
+  }
 }
index 3fdcc998ab841bdc9c5893517e2a813eda4309f3..c6e22f9443517d8b733c637712213d33afa892e3 100644 (file)
@@ -24,6 +24,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.ServerComponent;
 import org.sonar.api.security.DefaultGroups;
+import org.sonar.api.utils.MessageException;
 import org.sonar.core.permission.ComponentPermissionFacade;
 import org.sonar.core.permission.Permission;
 import org.sonar.core.user.*;
@@ -71,6 +72,12 @@ public class InternalPermissionService implements ServerComponent {
     }
   }
 
+  public void checkAtLeatOneSysAdminExists(){
+    if (roleDao.countUserWithPermission(Permission.SYSTEM_ADMIN.key()) == 0){
+      throw new MessageException(null, "global_permissions.error.need_at_lest_one_admin", null);
+    }
+  }
+
   private void applyPermissionTemplate(String templateKey, String componentId) {
     permissionFacade.applyPermissionTemplate(templateKey, Long.parseLong(componentId));
   }
index f2d5fd5d8785f3904aeacadc8dcee72bc1d87c44..1105fa5292ba1cfc10d5512006474fd87eb07540 100644 (file)
@@ -23,7 +23,7 @@ class GroupsController < ApplicationController
   before_filter :admin_required
 
   def index
-    @groups = Group.find(:all, :order => 'name')
+    @groups = Group.all(:order => 'name')
     if params[:id]
       @group = Group.find(params[:id])
     else
@@ -72,7 +72,7 @@ class GroupsController < ApplicationController
   end
 
   def to_index(errors, id)
-    if !errors.empty?
+    unless errors.empty?
       flash[:error] = errors.full_messages.join("<br/>\n")
     end
 
index 97807f9a7498c98b2c56a8fd59e0c282f3ec3250..98bcbad7e300fcceb8ef5d3291f7cbd6b1eba20b 100644 (file)
@@ -130,17 +130,16 @@ class UsersController < ApplicationController
   end
 
   def destroy
-    @user = User.find(params[:id])
-
-    if current_user.id==@user.id
-      flash[:error] = 'Please log in with another user in order to delete yourself.'
-
-    else
-      Api.users.deactivate(@user.login)
+    begin
+      user = User.find(params[:id])
+      Api.users.deactivate(user.login)
       flash[:notice] = 'User is deleted.'
+    rescue Java::OrgSonarServerExceptions::HttpException => exception
+      error = exception.cause
+      flash[:error] = (error.getMessage ? error.getMessage : Api::Utils.message(error.l10nKey, :params => error.l10nParams.to_a))
     end
 
-    to_index(@user.errors, nil)
+    redirect_to(:action => 'index', :id => nil)
   end
 
   def select_group
@@ -158,7 +157,7 @@ class UsersController < ApplicationController
   end
 
   def to_index(errors, id)
-    if !errors.empty?
+    unless errors.empty?
       flash[:error] = errors.full_messages.join("<br/>\n")
     end
 
index 773170b1a385305edc587070be00d2b00ab1baa4..501fd589a246ce89487923808420cfcae76ae4a5 100644 (file)
@@ -320,8 +320,8 @@ function openModalWindow(url, options) {
                 $j('input[type=submit]', obj).removeAttr('disabled');
                 errorElt.show();
                 errorElt.html(xhr.responseText);
-                // otherwise replace modal window by the returned text
               } else {
+                // otherwise replace modal window by the returned text
                 $j("#modal").html(xhr.responseText);
               }
             }
index 6dd2f92fca610aa018147275e24a37ba5e344f37..04d8daceb6adffd1771d226935a37687ffccbfd2 100644 (file)
@@ -30,6 +30,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.api.security.DefaultGroups;
+import org.sonar.api.utils.MessageException;
 import org.sonar.core.permission.ComponentPermissionFacade;
 import org.sonar.core.permission.Permission;
 import org.sonar.core.user.*;
@@ -40,6 +41,8 @@ import org.sonar.server.user.MockUserSession;
 
 import java.util.Map;
 
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.*;
@@ -191,6 +194,22 @@ public class InternalPermissionServiceTest {
     verify(permissionFacade).applyPermissionTemplate("my_template_key", 3L);
   }
 
+  @Test
+  public void should_check_at_least_one_sys_admin_exists() throws Exception {
+    // One admin exists -> all is ok
+    when(roleDao.countUserWithPermission(anyString())).thenReturn(1);
+    service.checkAtLeatOneSysAdminExists();
+
+    // No admin -> fail
+    try {
+      when(roleDao.countUserWithPermission(anyString())).thenReturn(0);
+      service.checkAtLeatOneSysAdminExists();
+      fail();
+    } catch (Exception e){
+      assertThat(e).isInstanceOf(MessageException.class);
+    }
+  }
+
   protected static class MatchesUserRole extends BaseMatcher<UserRoleDto> {
 
     private final UserRoleDto referenceDto;