]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4099 Issues & Measures filters should also be sharable only by users who have...
authorJulien Lancelot <julien.lancelot@gmail.com>
Wed, 17 Jul 2013 13:00:36 +0000 (15:00 +0200)
committerJulien Lancelot <julien.lancelot@gmail.com>
Wed, 17 Jul 2013 13:00:47 +0000 (15:00 +0200)
plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
sonar-server/src/main/java/org/sonar/server/issue/IssueFilterService.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/api/api_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/controllers/application_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/controllers/measures_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/views/issues/_filter_shared_form.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/measures/_shared_form.html.erb
sonar-server/src/test/java/org/sonar/server/issue/InternalRubyIssueServiceTest.java
sonar-server/src/test/java/org/sonar/server/issue/IssueFilterServiceTest.java

index 478ad0509a819c034152e2e021b36aab29055a8c..29a875ae924bba3eb765983a1c35abc14210df47 100644 (file)
@@ -2234,8 +2234,8 @@ global_permissions.admin=System Administration
 global_permissions.admin.desc=Ability to perform all administration functions for the instance: global configuration and personalization of default dashboards.
 global_permissions.profileadmin=Quality Profile Administration
 global_permissions.profileadmin.desc=Ability to perform any action on the quality profiles.
-global_permissions.shareDashboard=Dashboard Sharing
-global_permissions.shareDashboard.desc=Ability to share dashboards that any user will be able to follow.
+global_permissions.shareDashboard=Dashboard And Filter Sharing
+global_permissions.shareDashboard.desc=Ability to share dashboards, issue filters and measure filters.
 global_permissions.scan=Analysis Execution
 global_permissions.scan.desc=Ability to execute analyses, and to get all settings required to perform the analysis, even the secured ones like the scm account password, the jira account password, and so on.
 global_permissions.dryRunScan=Local (dry run) Analysis Execution
index 7d657d98751a28bb89190dc66956cb07e0f5b95d..b96f8658b4c99120b93fa3cc69190ecd7dd05cf9 100644 (file)
@@ -411,6 +411,10 @@ public class InternalRubyIssueService implements ServerComponent {
     }
   }
 
+  public boolean canUserShareIssueFilter(){
+    return issueFilterService.canShareFilter(UserSession.get());
+  }
+
   public String serializeFilterQuery(Map<String, Object> filterQuery) {
     return issueFilterService.serializeFilterQuery(filterQuery);
   }
index 622607aa66c4ad6fde5b73c8ddce950e42211884..a9ae87ab0d3b7fec013003354cf48841df14a6f5 100644 (file)
@@ -93,19 +93,19 @@ public class IssueFilterService implements ServerComponent {
   public DefaultIssueFilter save(DefaultIssueFilter issueFilter, UserSession userSession) {
     String user = getLoggedLogin(userSession);
     issueFilter.setUser(user);
-    validateFilter(issueFilter);
+    validateFilter(issueFilter, userSession);
     return insertIssueFilter(issueFilter, user);
   }
 
   public DefaultIssueFilter update(DefaultIssueFilter issueFilter, UserSession userSession) {
     String login = getLoggedLogin(userSession);
     IssueFilterDto existingFilterDto = findIssueFilterDto(issueFilter.id(), login);
-    verifyCurrentUserCanModifyFilter(existingFilterDto.toIssueFilter(), login);
+    verifyCurrentUserCanModifyFilter(existingFilterDto.toIssueFilter(), userSession);
     verifyCurrentUserCanChangeFilterSharingFilter(issueFilter, existingFilterDto, login);
     if (!existingFilterDto.getUserLogin().equals(issueFilter.user())) {
-      verifyCurrentUserCanChangeFilterOwnership(login);
+      verifyCurrentUserCanChangeFilterOwnership(userSession);
     }
-    validateFilter(issueFilter);
+    validateFilter(issueFilter, userSession);
     deleteOtherFavoriteFiltersIfFilterBecomeUnshared(existingFilterDto, issueFilter);
     filterDao.update(IssueFilterDto.toIssueFilter(issueFilter));
     return issueFilter;
@@ -124,7 +124,7 @@ public class IssueFilterService implements ServerComponent {
   public DefaultIssueFilter updateFilterQuery(Long issueFilterId, Map<String, Object> filterQuery, UserSession userSession) {
     String login = getLoggedLogin(userSession);
     IssueFilterDto issueFilterDto = findIssueFilterDto(issueFilterId, login);
-    verifyCurrentUserCanModifyFilter(issueFilterDto.toIssueFilter(), login);
+    verifyCurrentUserCanModifyFilter(issueFilterDto.toIssueFilter(), userSession);
 
     DefaultIssueFilter issueFilter = issueFilterDto.toIssueFilter();
     issueFilter.setData(serializeFilterQuery(filterQuery));
@@ -135,7 +135,7 @@ public class IssueFilterService implements ServerComponent {
   public void delete(Long issueFilterId, UserSession userSession) {
     String login = getLoggedLogin(userSession);
     IssueFilterDto issueFilterDto = findIssueFilterDto(issueFilterId, login);
-    verifyCurrentUserCanModifyFilter(issueFilterDto.toIssueFilter(), login);
+    verifyCurrentUserCanModifyFilter(issueFilterDto.toIssueFilter(), userSession);
 
     deleteFavouriteIssueFilters(issueFilterDto);
     filterDao.delete(issueFilterId);
@@ -144,9 +144,11 @@ public class IssueFilterService implements ServerComponent {
   public DefaultIssueFilter copy(Long issueFilterIdToCopy, DefaultIssueFilter issueFilter, UserSession userSession) {
     String login = getLoggedLogin(userSession);
     IssueFilterDto issueFilterDtoToCopy = findIssueFilterDto(issueFilterIdToCopy, login);
+    // Copy of filter should not be shared
+    issueFilterDtoToCopy.setShared(false);
     issueFilter.setUser(login);
     issueFilter.setData(issueFilterDtoToCopy.getData());
-    validateFilter(issueFilter);
+    validateFilter(issueFilter, userSession);
     return insertIssueFilter(issueFilter, login);
   }
 
@@ -198,9 +200,13 @@ public class IssueFilterService implements ServerComponent {
     return issueFilterDto;
   }
 
+  public boolean canShareFilter(UserSession userSession){
+    return userSession.hasGlobalPermission(Permission.DASHBOARD_SHARING);
+  }
+
   String getLoggedLogin(UserSession userSession) {
     String user = userSession.login();
-    if (!userSession.isLoggedIn() && user != null) {
+    if (!userSession.isLoggedIn()) {
       throw new UnauthorizedException("User is not logged in");
     }
     return user;
@@ -212,8 +218,8 @@ public class IssueFilterService implements ServerComponent {
     }
   }
 
-  private void verifyCurrentUserCanModifyFilter(DefaultIssueFilter issueFilter, String user) {
-    if (!issueFilter.user().equals(user) && !isAdmin(user)) {
+  private void verifyCurrentUserCanModifyFilter(DefaultIssueFilter issueFilter, UserSession userSession) {
+    if (!issueFilter.user().equals(userSession.login()) && !isAdmin(userSession)) {
       throw new ForbiddenException("User is not authorized to modify this filter");
     }
   }
@@ -224,13 +230,19 @@ public class IssueFilterService implements ServerComponent {
     }
   }
 
-  private void verifyCurrentUserCanChangeFilterOwnership(String user) {
-    if (!isAdmin(user)) {
+  private void verifyCurrentUserCanChangeFilterOwnership(UserSession userSession) {
+    if (!isAdmin(userSession)) {
       throw new ForbiddenException("User is not authorized to change the owner of this filter");
     }
   }
 
-  private void validateFilter(final DefaultIssueFilter issueFilter) {
+  private void verifyCurrentUserCanShareFilter(DefaultIssueFilter issueFilter, UserSession userSession) {
+    if (issueFilter.shared() && !canShareFilter(userSession)) {
+      throw new ForbiddenException("User is not authorized to share this filter");
+    }
+  }
+
+  private void validateFilter(final DefaultIssueFilter issueFilter, UserSession userSession) {
     List<IssueFilterDto> userFilters = selectUserIssueFilters(issueFilter.user());
     IssueFilterDto userFilterSameName = findFilterWithSameName(userFilters, issueFilter.name());
     if (userFilterSameName != null && !userFilterSameName.getId().equals(issueFilter.id())) {
@@ -242,6 +254,7 @@ public class IssueFilterService implements ServerComponent {
       if (sharedFilterWithSameName != null && !sharedFilterWithSameName.getId().equals(issueFilter.id())) {
         throw new BadRequestException("Other users already share filters with the same name");
       }
+      verifyCurrentUserCanShareFilter(issueFilter, userSession);
     }
   }
 
@@ -308,8 +321,8 @@ public class IssueFilterService implements ServerComponent {
     }));
   }
 
-  private boolean isAdmin(String user) {
-    return authorizationDao.selectGlobalPermissions(user).contains(Permission.SYSTEM_ADMIN.key());
+  private boolean isAdmin(UserSession userSession) {
+    return userSession.hasGlobalPermission(Permission.SYSTEM_ADMIN);
   }
 
   private IssueFilterResult createIssueFilterResult(IssueQueryResult issueQueryResult, IssueQuery issueQuery) {
index 80976c8ae09fe705145519d4f432b1db321c4735..2e09ab554eab3d52b7bea6b2e7dc0b5bf03e701b 100644 (file)
@@ -78,7 +78,7 @@ class Api::ApiController < ApplicationController
   #
   #
 
-  def render_native_access_denied
+  def render_native_access_denied(exception)
     render_access_denied
   end
 
index 869c6b61458eb52c483b9ecac552fbaa14083fca..fb7fa6d6b2be8bd77cfd10fefcfebb79dcb42938 100644 (file)
@@ -174,17 +174,21 @@ class ApplicationController < ActionController::Base
     render :text => message, :status => exception.httpCode
   end
 
-  def render_native_access_denied
-    access_denied
+  def render_native_access_denied(exception)
+    if request.xhr?
+      render_server_exception(exception)
+    else
+      access_denied
+    end
   end
 
   def render_native_exception(error)
     if error.cause.java_kind_of? Java::JavaLang::IllegalArgumentException
       render_bad_request(error.cause.getMessage)
     elsif error.cause.java_kind_of? Java::OrgSonarServerExceptions::UnauthorizedException
-      render_native_access_denied
+      render_native_access_denied(error.cause)
     elsif error.cause.java_kind_of? Java::OrgSonarServerExceptions::ForbiddenException
-      render_native_access_denied
+      render_native_access_denied(error.cause)
     elsif error.cause.java_kind_of? Java::OrgSonarServerExceptions::HttpException
       render_server_exception(error.cause)
     else
index 078f69f9f279d29ff75a15b58834663181f4bb2d..e83daf4af9a32171fc78ceac046ea6f980e44fdd 100644 (file)
@@ -85,7 +85,7 @@ class MeasuresController < ApplicationController
     end
     @filter.name=params[:name]
     @filter.description=params[:description]
-    @filter.shared=(params[:shared]=='true')
+    @filter.shared=(params[:shared]=='true') && has_role?(:shareDashboard)
     @filter.data=URI.unescape(params[:data])
     if @filter.save
       current_user.favourited_measure_filters<<@filter if add_to_favourites
@@ -138,7 +138,7 @@ class MeasuresController < ApplicationController
 
     @filter.name=params[:name]
     @filter.description=params[:description]
-    @filter.shared=(params[:shared]=='true')
+    @filter.shared=(params[:shared]=='true') && has_role?(:shareDashboard)
     if has_role?(:admin) && params[:owner]
       @filter.user = User.find_by_login(params[:owner])
     end
@@ -171,7 +171,8 @@ class MeasuresController < ApplicationController
     target.name=params[:name]
     target.description=params[:description]
     target.user_id=current_user.id
-    target.shared=(params[:shared]=='true')
+    # Copy of filter should never be shared
+    target.shared=false
     target.data=source.data
     if target.save
       current_user.favourited_measure_filters << target
index d595611f70e7cd36d7c75e191b052fa91fcb4f65..5945781f885e3c8fd19ccfd4355770e401bd7cae 100644 (file)
   <% else %>
     <input id="user" name="user" type="hidden" value="<%= h(@filter.user) if @filter -%>"/>
   <% end %>
-  <div class="modal-field">
-    <% if !@filter || @filter.user == current_user.login %>
-      <label for="shared"><%= message('issue_filter.form.share') -%></label>
-      <input id="shared" name="shared" type="checkbox" value="true" <%= 'checked' if (@filter && @filter.shared) -%>/>
-    <% else %>
-      <input id="shared" name="shared" type="hidden" value="<%= @filter.shared if @filter -%>"/>
-    <% end %>
-  </div>
+  <% if Internal.issues.canUserShareIssueFilter() %>
+    <div class="modal-field">
+      <% if !@filter || @filter.user == current_user.login %>
+        <label for="shared"><%= message('issue_filter.form.share') -%></label>
+        <input id="shared" name="shared" type="checkbox" value="true" <%= 'checked' if (@filter && @filter.shared) -%>/>
+      <% else %>
+        <input id="shared" name="shared" type="hidden" value="<%= @filter.shared if @filter -%>"/>
+      <% end %>
+    </div>
+  <% end %>
 </div>
\ No newline at end of file
index f5a6ed43dbe873cec6f3782ad46961d540459a1e..6b551d48a3849fbcc3e7db31a5ee0af53f9d0567 100644 (file)
       <%= user_select_tag('owner', :html_id => 'select-filter-owner', :selected_user => @filter.user) -%>
     </div>
   <% end %>
-  <% if @filter.user_id.nil? || @filter.user_id == current_user.id %>
-    <div class="modal-field">
-      <label for="shared"><%= h message('measure_filter.shared_with_all_users') -%></label>
-      <input id="shared" name="shared" type="checkbox" value="true" <%= 'checked' if @filter.shared -%>/>
-    </div>
-  <% else %>
-    <input id="shared" name="shared" type="hidden" value="<%= @filter.shared -%>"/>
+  <% if has_role?(:shareDashboard) %>
+    <% if @filter.user_id.nil? || @filter.user_id == current_user.id %>
+      <div class="modal-field">
+        <label for="shared"><%= h message('measure_filter.shared_with_all_users') -%></label>
+        <input id="shared" name="shared" type="checkbox" value="true" <%= 'checked' if @filter.shared -%>/>
+      </div>
+    <% else %>
+      <input id="shared" name="shared" type="hidden" value="<%= @filter.shared -%>"/>
+    <% end %>
   <% end %>
 </div>
\ No newline at end of file
index 95dcdb4aac2667133297a57efa087d696c56b751..82849fe0371874c6bafa56deb8506bbedbdc59a9 100644 (file)
@@ -531,13 +531,19 @@ public class InternalRubyIssueServiceTest {
   }
 
   @Test
-  public void should_check_is_user_is_authorized_to_see_issue_filter() {
+  public void should_check_if_user_is_authorized_to_see_issue_filter() {
     DefaultIssueFilter issueFilter = new DefaultIssueFilter();
     service.isUserAuthorized(issueFilter);
     verify(issueFilterService).getLoggedLogin(any(UserSession.class));
     verify(issueFilterService).verifyCurrentUserCanReadFilter(eq(issueFilter), anyString());
   }
 
+  @Test
+  public void should_check_if_user_can_share_issue_filter(){
+    service.canUserShareIssueFilter();
+    verify(issueFilterService).canShareFilter(any(UserSession.class));
+  }
+
   @Test
   public void should_execute_bulk_change() {
     Map<String, Object> params = newHashMap();
index 49a5cbd5da273033d3c2e3f8f5014e12b43c223e..16f5948ae1b9dd7e8d3f84d00b739f7bd924e41b 100644 (file)
@@ -74,10 +74,7 @@ public class IssueFilterServiceTest {
 
   @Before
   public void before() {
-    userSession = mock(UserSession.class);
-    when(userSession.isLoggedIn()).thenReturn(true);
-    when(userSession.userId()).thenReturn(10);
-    when(userSession.login()).thenReturn("john");
+    userSession = MockUserSession.create().setLogin("john");
 
     issueFilterDao = mock(IssueFilterDao.class);
     issueFilterFavouriteDao = mock(IssueFilterFavouriteDao.class);
@@ -131,7 +128,7 @@ public class IssueFilterServiceTest {
 
   @Test
   public void should_not_find_by_id_if_not_logged() {
-    when(userSession.isLoggedIn()).thenReturn(false);
+    UserSession userSession = MockUserSession.create().setLogin(null);
     try {
       service.find(1L, userSession);
       fail();
@@ -165,7 +162,7 @@ public class IssueFilterServiceTest {
 
   @Test
   public void should_not_find_by_user_if_not_logged() {
-    when(userSession.isLoggedIn()).thenReturn(false);
+    UserSession userSession = MockUserSession.create().setLogin(null);
     try {
       service.findByUser(userSession);
       fail();
@@ -195,7 +192,7 @@ public class IssueFilterServiceTest {
 
   @Test
   public void should_not_save_if_not_logged() {
-    when(userSession.isLoggedIn()).thenReturn(false);
+    UserSession userSession = MockUserSession.create().setLogin(null);
     try {
       DefaultIssueFilter issueFilter = new DefaultIssueFilter().setName("My Issue");
       service.save(issueFilter, userSession);
@@ -244,20 +241,35 @@ public class IssueFilterServiceTest {
   }
 
   @Test
-  public void should_update_sharing() {
-    when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Filter").setShared(true).setUserLogin("john"));
+  public void should_have_permission_to_share_filter() {
+    UserSession userSession = MockUserSession.create().setLogin("john").setPermissions(Permission.DASHBOARD_SHARING);
+    when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Filter").setShared(false).setUserLogin("john"));
 
-    DefaultIssueFilter result = service.update(new DefaultIssueFilter().setId(1L).setName("My Filter").setShared(false).setUser("john"), userSession);
-    assertThat(result.shared()).isFalse();
+    DefaultIssueFilter result = service.update(new DefaultIssueFilter().setId(1L).setName("My Filter").setShared(true).setUser("john"), userSession);
+    assertThat(result.shared()).isTrue();
 
     verify(issueFilterDao).update(any(IssueFilterDto.class));
   }
 
+  @Test
+  public void should_not_share_filter_if_no_permission() {
+    UserSession userSession = MockUserSession.create().setLogin("john").setPermissions();
+    when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Filter").setShared(false).setUserLogin("john"));
+
+    try {
+      service.update(new DefaultIssueFilter().setId(1L).setName("My Filter").setShared(true).setUser("john"), userSession);
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(ForbiddenException.class).hasMessage("User is not authorized to share this filter");
+    }
+    verify(issueFilterDao, never()).update(any(IssueFilterDto.class));
+  }
+
   @Test
   public void should_not_update_sharing_if_not_owner() {
     // John is admin and want to change arthur filter sharing
+    UserSession userSession = MockUserSession.create().setLogin("john").setPermissions(Permission.SYSTEM_ADMIN);
     when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("Arthur Filter").setShared(true).setUserLogin("arthur"));
-    when(authorizationDao.selectGlobalPermissions("john")).thenReturn(newArrayList(Permission.SYSTEM_ADMIN.key()));
 
     try {
       service.update(new DefaultIssueFilter().setId(1L).setName("Arthur Filter").setShared(false).setUser("john"), userSession);
@@ -297,7 +309,7 @@ public class IssueFilterServiceTest {
 
   @Test
   public void should_update_other_shared_filter_if_admin() {
-    when(authorizationDao.selectGlobalPermissions("john")).thenReturn(newArrayList(Permission.SYSTEM_ADMIN.key()));
+    UserSession userSession = MockUserSession.create().setLogin("john").setPermissions(Permission.SYSTEM_ADMIN, Permission.DASHBOARD_SHARING);
     when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Old Filter").setDescription("Old description").setUserLogin("arthur").setShared(true));
 
     DefaultIssueFilter result = service.update(new DefaultIssueFilter().setId(1L).setName("My New Filter").setDescription("New description").setShared(true), userSession);
@@ -368,12 +380,13 @@ public class IssueFilterServiceTest {
     IssueFilterDto sharedFilter = new IssueFilterDto().setId(1L).setName("My filter").setUserLogin("former.owner").setShared(true);
     IssueFilterDto expectedDto = new IssueFilterDto().setName("My filter").setUserLogin("new.owner").setShared(true);
 
-    when(authorizationDao.selectGlobalPermissions(currentUser)).thenReturn(newArrayList(Permission.SYSTEM_ADMIN.key()));
+    UserSession userSession = MockUserSession.create().setLogin(currentUser).setPermissions(Permission.SYSTEM_ADMIN, Permission.DASHBOARD_SHARING);
+
     when(issueFilterDao.selectById(1L)).thenReturn(sharedFilter);
     when(issueFilterDao.selectSharedFilters()).thenReturn(Lists.newArrayList(sharedFilter));
 
     DefaultIssueFilter issueFilter = new DefaultIssueFilter().setId(1L).setName("My filter").setShared(true).setUser("new.owner");
-    service.update(issueFilter, MockUserSession.create().setUserId(1).setLogin(currentUser));
+    service.update(issueFilter, userSession);
 
     verify(issueFilterDao).update(argThat(Matches.filter(expectedDto)));
   }
@@ -432,7 +445,7 @@ public class IssueFilterServiceTest {
 
   @Test
   public void should_delete_shared_filter_if_user_is_admin() {
-    when(authorizationDao.selectGlobalPermissions("john")).thenReturn(newArrayList(Permission.SYSTEM_ADMIN.key()));
+    UserSession userSession = MockUserSession.create().setLogin("john").setPermissions(Permission.SYSTEM_ADMIN);
     when(issueFilterDao.selectById(1L)).thenReturn(new IssueFilterDto().setId(1L).setName("My Issues").setUserLogin("arthur").setShared(true));
 
     service.delete(1L, userSession);
@@ -489,6 +502,7 @@ public class IssueFilterServiceTest {
     DefaultIssueFilter result = service.copy(1L, issueFilter, userSession);
     assertThat(result.name()).isEqualTo("Copy Of My Issue");
     assertThat(result.user()).isEqualTo("john");
+    assertThat(result.shared()).isFalse();
 
     verify(issueFilterDao).insert(any(IssueFilterDto.class));
   }
@@ -535,7 +549,7 @@ public class IssueFilterServiceTest {
 
   @Test
   public void should_not_find_favourite_issue_filter_if_not_logged() {
-    when(userSession.isLoggedIn()).thenReturn(false);
+    UserSession userSession = MockUserSession.create().setLogin(null);
 
     try {
       service.findFavoriteFilters(userSession);