]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3755 add authorization to search of issues
authorSimon Brandhof <simon.brandhof@gmail.com>
Sun, 14 Apr 2013 21:39:12 +0000 (23:39 +0200)
committerSimon Brandhof <simon.brandhof@gmail.com>
Sun, 14 Apr 2013 21:39:12 +0000 (23:39 +0200)
20 files changed:
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueFinder.java
sonar-core/src/main/java/org/sonar/core/issue/IssueDao.java
sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java
sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java
sonar-core/src/main/java/org/sonar/core/user/AuthorizationDao.java [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/core/issue/IssueMapper.xml
sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueFinderTest.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/Issuable.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueFinder.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueQuery.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/JRubyIssues.java
sonar-server/src/main/java/org/sonar/server/issue/DefaultJRubyIssues.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/main/java/org/sonar/server/ui/DefaultJRubyIssues.java [deleted file]
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacades.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/api/issues_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/controllers/issues_controller.rb
sonar-server/src/test/java/org/sonar/server/issue/DefaultJRubyIssuesTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/ui/DefaultJRubyIssuesTest.java [deleted file]
sonar-server/src/test/java/org/sonar/server/ui/JRubyFacadesTest.java

index 3689c541f600d3b6c894a4ea48d2e65ddefeff76..12d965cfdefb0165ed4c29e47ef710d6934edc2e 100644 (file)
@@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import org.apache.ibatis.session.SqlSession;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.issue.Issue;
@@ -33,8 +34,12 @@ import org.sonar.api.issue.IssueQuery;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.resource.ResourceDao;
 import org.sonar.core.resource.ResourceDto;
+import org.sonar.core.user.AuthorizationDao;
+
+import javax.annotation.Nullable;
 
 import java.util.List;
 import java.util.Map;
@@ -47,53 +52,70 @@ public class DefaultIssueFinder implements IssueFinder {
 
   private static final Logger LOG = LoggerFactory.getLogger(DefaultIssueFinder.class);
 
+  /**
+   * The role required to access issues
+   */
+  private static final String ROLE = "user";
+
+  private final MyBatis myBatis;
   private final IssueDao issueDao;
   private final ResourceDao resourceDao;
+  private final AuthorizationDao authorizationDao;
   private final RuleFinder ruleFinder;
 
-  public DefaultIssueFinder(IssueDao issueDao, ResourceDao resourceDao, RuleFinder ruleFinder) {
+  public DefaultIssueFinder(MyBatis myBatis, IssueDao issueDao, ResourceDao resourceDao,
+                            AuthorizationDao authorizationDao, RuleFinder ruleFinder) {
+    this.myBatis = myBatis;
     this.issueDao = issueDao;
     this.resourceDao = resourceDao;
+    this.authorizationDao = authorizationDao;
     this.ruleFinder = ruleFinder;
   }
 
-  public Results find(IssueQuery query) {
+  public Results find(IssueQuery query, @Nullable Integer currentUserId) {
     LOG.debug("IssueQuery : {}", query);
-    List<IssueDto> dtoList = issueDao.select(query);
-
-    final Set<Integer> componentIds = Sets.newLinkedHashSet();
-    final Set<Integer> ruleIds = Sets.newLinkedHashSet();
-    for (IssueDto dto : dtoList) {
-      componentIds.add(dto.getResourceId());
-      ruleIds.add(dto.getRuleId());
-    }
-    final Map<Integer, Rule> rules = Maps.newHashMap();
-    for (Integer ruleId : ruleIds) {
-      Rule rule = ruleFinder.findById(ruleId);
-      if (rule != null) {
-        rules.put(rule.getId(), rule);
+    SqlSession sqlSession = myBatis.openSession();
+    try {
+      List<IssueDto> issueDtos = issueDao.select(query, sqlSession);
+
+      Set<Integer> componentIds = Sets.newLinkedHashSet();
+      Set<Integer> ruleIds = Sets.newLinkedHashSet();
+      for (IssueDto issueDto : issueDtos) {
+        componentIds.add(issueDto.getResourceId());
+        ruleIds.add(issueDto.getRuleId());
       }
-    }
-    final Map<Integer, ResourceDto> resources = Maps.newHashMap();
-    for (Integer componentId : componentIds) {
-      ResourceDto resource = resourceDao.getResource(componentId);
-      if (resource != null) {
-        resources.put(resource.getId().intValue(), resource);
-      }
-    }
 
-    // TODO verify authorization
+      componentIds = authorizationDao.keepAuthorizedComponentIds(componentIds, currentUserId, ROLE, sqlSession);
 
-    List<Issue> issues = ImmutableList.copyOf(Iterables.transform(dtoList, new Function<IssueDto, Issue>() {
-      @Override
-      public Issue apply(IssueDto dto) {
-        Rule rule = rules.get(dto.getRuleId());
-        ResourceDto resource = resources.get(dto.getResourceId());
-        return toIssue(dto, rule, resource);
+      final Map<Integer, Rule> rules = Maps.newHashMap();
+      for (Integer ruleId : ruleIds) {
+        Rule rule = ruleFinder.findById(ruleId);
+        if (rule != null) {
+          rules.put(rule.getId(), rule);
+        }
+      }
+      final Map<Integer, ResourceDto> resources = Maps.newHashMap();
+      for (Integer componentId : componentIds) {
+        // TODO replace N+1 SQL requests by a single one
+        ResourceDto resource = resourceDao.getResource(componentId);
+        if (resource != null) {
+          resources.put(resource.getId().intValue(), resource);
+        }
       }
-    }));
 
-    return new DefaultResults(issues);
+      List<Issue> issues = ImmutableList.copyOf(Iterables.transform(issueDtos, new Function<IssueDto, Issue>() {
+        @Override
+        public Issue apply(IssueDto dto) {
+          Rule rule = rules.get(dto.getRuleId());
+          ResourceDto resource = resources.get(dto.getResourceId());
+          return toIssue(dto, rule, resource);
+        }
+      }));
+
+      return new DefaultResults(issues);
+    } finally {
+      MyBatis.closeQuietly(sqlSession);
+    }
   }
 
   public Issue findByKey(String key) {
index 2962518a8c431be59697afdef30bc164d26db887..c1691a7a3f970f7f627e28856b8346c81e72398f 100644 (file)
@@ -88,13 +88,16 @@ public class IssueDao implements BatchComponent, ServerComponent {
   public List<IssueDto> select(IssueQuery query) {
     SqlSession session = mybatis.openSession();
     try {
-      // TODO support ordering
-
-      return session.selectList("org.sonar.core.issue.IssueMapper.select", query, new RowBounds(query.offset(), query.limit()));
-
+      return select(query, session);
     } finally {
       MyBatis.closeQuietly(session);
     }
   }
 
+  public List<IssueDto> select(IssueQuery query, SqlSession session) {
+    // TODO support ordering
+
+    return session.selectList("org.sonar.core.issue.IssueMapper.select", query, new RowBounds(query.offset(), query.limit()));
+  }
+
 }
index 8cc38c58d8395532bcccaf6a31e14c96f31a7b4d..5c2ddfb0ac9db924b36eb397aaa644a090c6475d 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.core.rule.RuleDao;
 import org.sonar.core.source.jdbc.SnapshotDataDao;
 import org.sonar.core.template.LoadedTemplateDao;
 import org.sonar.core.user.AuthorDao;
+import org.sonar.core.user.AuthorizationDao;
 import org.sonar.core.user.UserDao;
 
 import java.util.List;
@@ -51,6 +52,7 @@ public final class DaoUtils {
     return ImmutableList.of(
       ActiveDashboardDao.class,
       AuthorDao.class,
+      AuthorizationDao.class,
       DashboardDao.class,
       DuplicationDao.class,
       GraphDao.class,
index b337376e9ec1040b4d0ec338bd0878702a4f6a73..05706a7303d41fd5bfcd529699cfee6cdf92e5a0 100644 (file)
@@ -134,6 +134,7 @@ public class MyBatis implements BatchComponent, ServerComponent {
     };
     loadMappers(conf, mappers);
     loadMapper(conf, "org.sonar.core.issue.IssueMapper");
+    loadMapper(conf, "org.sonar.core.user.AuthorizationMapper");
     configureLogback(mappers);
 
     sessionFactory = new SqlSessionFactoryBuilder().build(conf);
diff --git a/sonar-core/src/main/java/org/sonar/core/user/AuthorizationDao.java b/sonar-core/src/main/java/org/sonar/core/user/AuthorizationDao.java
new file mode 100644 (file)
index 0000000..9c1cbdd
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.core.user;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.ServerComponent;
+import org.sonar.core.persistence.MyBatis;
+
+import javax.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public class AuthorizationDao implements ServerComponent {
+
+  private final MyBatis mybatis;
+
+  public AuthorizationDao(MyBatis mybatis) {
+    this.mybatis = mybatis;
+  }
+
+  public Set<Integer> keepAuthorizedComponentIds(Set<Integer> componentIds, @Nullable Integer userId, String role) {
+    SqlSession session = mybatis.openSession();
+    try {
+      return keepAuthorizedComponentIds(componentIds, userId, role, session);
+
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public Set<Integer> keepAuthorizedComponentIds(Set<Integer> componentIds, @Nullable Integer userId, String role, SqlSession session) {
+    if (componentIds.isEmpty()) {
+      return Collections.emptySet();
+    }
+    String sql;
+    Map<String, Object> params;
+    if (userId == null) {
+      sql = "keepAuthorizedComponentIdsForAnonymous";
+      params = ImmutableMap.of("role", role, "componentIds", componentIds);
+    } else {
+      sql = "keepAuthorizedComponentIdsForUser";
+      params = ImmutableMap.of("userId", userId, "role", role, "componentIds", componentIds);
+    }
+
+    return Sets.newHashSet(session.<Integer>selectList(sql, params));
+  }
+}
index c3b04c76b8ad0736a1c4ee87458bf3ae2255be32..e916edbc1a75152f05f51d13859b52c5b009a6cd 100644 (file)
@@ -28,7 +28,7 @@
     i.closed_at as closedAt
   </sql>
 
-  <insert id="insert" parameterType="Issue" useGeneratedKeys="true" keyProperty ="id">
+  <insert id="insert" parameterType="Issue" useGeneratedKeys="true" keyProperty="id">
     INSERT INTO issues (uuid, resource_id, rule_id, severity, manual_severity, manual_issue, title, message, line, cost, status,
     resolution, checksum, user_login, assignee_login, person_id, data, created_at, updated_at, closed_at)
     VALUES (#{uuid}, #{resourceId}, #{ruleId}, #{severity}, #{manualSeverity}, #{manualIssue}, #{title}, #{message}, #{line}, #{cost}, #{status},
@@ -36,8 +36,8 @@
   </insert>
 
   <!-- Oracle -->
-  <insert id="insert" databaseId="oracle" parameterType="Issue" keyColumn="id" useGeneratedKeys="true" keyProperty ="id">
-    <selectKey order="BEFORE" resultType="Long" keyProperty="id" >
+  <insert id="insert" databaseId="oracle" parameterType="Issue" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+    <selectKey order="BEFORE" resultType="Long" keyProperty="id">
       select issues_seq.NEXTVAL from DUAL
     </selectKey>
     INSERT INTO issues (id, uuid, resource_id, rule_id, severity, manual_severity, manual_issue, title, message, line, cost, status,
   </update>
 
   <select id="selectById" parameterType="long" resultType="Issue">
-    select <include refid="issueColumns"/>
+    select
+    <include refid="issueColumns"/>
     from issues i
     where i.id=#{id}
   </select>
 
   <select id="selectByKey" parameterType="String" resultType="Issue">
-    select <include refid="issueColumns"/>
+    select
+    <include refid="issueColumns"/>
     from issues i
     where i.uuid=#{uuid}
   </select>
 
   <select id="select" parameterType="map" resultType="Issue">
-    select <include refid="issueColumns"/> from issues i
+    select
+    <include refid="issueColumns"/>
+    <include refid="selectQueryConditions"/>
+  </select>
+
+  <sql id="selectQueryConditions">
+    from issues i
     <if test="components != null and components.size() > 0">
       , projects p, snapshots root, snapshots s
     </if>
         </foreach>
       </if>
     </where>
-  </select>
+  </sql>
 
 </mapper>
 
index c1a2447894de31ece74b7d6f557657c70c5fa4e2..a5e5be2a719c43df05ef42d553b8b7d3f9024d16 100644 (file)
  */
 package org.sonar.core.issue;
 
+import org.apache.ibatis.session.SqlSession;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.IssueFinder;
 import org.sonar.api.issue.IssueQuery;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
+import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.resource.ResourceDao;
 import org.sonar.core.resource.ResourceDto;
+import org.sonar.core.user.AuthorizationDao;
 
 import java.util.List;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anySet;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 public class DefaultIssueFinderTest {
 
-  private DefaultIssueFinder finder;
-  private IssueDao issueDao;
-  private ResourceDao resourceDao;
-  private RuleFinder ruleFinder;
+  MyBatis mybatis;
+  DefaultIssueFinder finder;
+  IssueDao issueDao;
+  ResourceDao resourceDao;
+  RuleFinder ruleFinder;
+  AuthorizationDao authorizationDao;
 
   @Before
   public void before() {
+    mybatis = mock(MyBatis.class);
     issueDao = mock(IssueDao.class);
     resourceDao = mock(ResourceDao.class);
     ruleFinder = mock(RuleFinder.class);
-    finder = new DefaultIssueFinder(issueDao, resourceDao, ruleFinder);
+    authorizationDao = mock(AuthorizationDao.class);
+    finder = new DefaultIssueFinder(mybatis, issueDao, resourceDao, authorizationDao, ruleFinder);
   }
 
   @Test
   public void should_find_issues() {
+    grantAccessRights();
     IssueQuery issueQuery = mock(IssueQuery.class);
 
     IssueDto issue1 = new IssueDto().setId(1L).setRuleId(50).setResourceId(123);
     IssueDto issue2 = new IssueDto().setId(2L).setRuleId(50).setResourceId(123);
     List<IssueDto> dtoList = newArrayList(issue1, issue2);
-    when(issueDao.select(issueQuery)).thenReturn(dtoList);
+    when(issueDao.select(eq(issueQuery), any(SqlSession.class))).thenReturn(dtoList);
     Rule rule = Rule.create("repo", "key");
     rule.setId(50);
     when(ruleFinder.findById(anyInt())).thenReturn(rule);
     when(resourceDao.getResource(anyInt())).thenReturn(new ResourceDto().setKey("componentKey").setId(123L));
 
-    IssueFinder.Results results = finder.find(issueQuery);
+    IssueFinder.Results results = finder.find(issueQuery, null);
     assertThat(results.issues()).hasSize(2);
     Issue issue = results.issues().iterator().next();
     assertThat(issue.componentKey()).isEqualTo("componentKey");
@@ -86,4 +100,14 @@ public class DefaultIssueFinderTest {
     assertThat(issue.ruleKey()).isEqualTo("key");
     assertThat(issue.ruleRepositoryKey()).isEqualTo("repo");
   }
+
+  private void grantAccessRights() {
+    when(authorizationDao.keepAuthorizedComponentIds(anySet(), anyInt(), anyString(), any(SqlSession.class)))
+      .thenAnswer(new Answer<Object>() {
+        @Override
+        public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
+          return invocationOnMock.getArguments()[0];
+        }
+      });
+  }
 }
index ecc8dc4cfebdca9cf11de5f819638bddb42e9c81..0daa5a692ee9f35b02fc899feb5f034b228a7d17 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.sonar.api.issue;
 
-import org.sonar.api.component.Component;
 import org.sonar.api.component.Perspective;
 
 import java.util.Collection;
@@ -51,8 +50,4 @@ public interface Issuable extends Perspective {
   IssueBuilder newIssue();
 
   Collection<Issue> issues();
-
-  @Override
-  Component component();
-
 }
index a71f62a8c8480cfcd8577289f4ecf047d6a32029..e158c6a1d24a463dc67c5b415e2a355598db7b1a 100644 (file)
@@ -24,6 +24,8 @@ import org.sonar.api.ServerComponent;
 import org.sonar.api.component.Component;
 import org.sonar.api.rules.Rule;
 
+import javax.annotation.Nullable;
+
 import java.util.Collection;
 import java.util.List;
 
@@ -36,7 +38,7 @@ public interface IssueFinder extends ServerComponent {
     List<Issue> issues();
   }
 
-  Results find(IssueQuery issueQuery);
+  Results find(IssueQuery issueQuery, @Nullable Integer currentUserId);
 
   Issue findByKey(String key);
 
index e34b867a1c44fd2c4e26751ce230f4580c18de93..c227aed75baab79ec0110391a51b4ad98adac76e 100644 (file)
@@ -26,6 +26,8 @@ import org.apache.commons.lang.builder.ReflectionToStringBuilder;
 import java.util.List;
 
 /**
+ * TODO add javadoc
+ *
  * @since 3.6
  */
 public class IssueQuery {
index 3cb966e800a42c86df6f41474fc147d266b2af81..af3a0a0bffc49b67701dd23b1cd0dd61f320e4e6 100644 (file)
@@ -37,7 +37,7 @@ public interface JRubyIssues extends ServerComponent {
    * Search for issues.
    *
    * <p>
-   *   Ruby: <code>Api.issues.find(hash)</code>
+   *   Ruby: <code>Api.issues.find(hash_of_parameters, current_user.id)</code>
    * </p>
    *
    * <p>Parameters</p>
@@ -45,6 +45,6 @@ public interface JRubyIssues extends ServerComponent {
    *   TODO document parameters
    * </ul>
    */
-  IssueFinder.Results find(Map<String, Object> parameters);
+  IssueFinder.Results find(Map<String, Object> parameters, Integer currentUserId);
 
 }
diff --git a/sonar-server/src/main/java/org/sonar/server/issue/DefaultJRubyIssues.java b/sonar-server/src/main/java/org/sonar/server/issue/DefaultJRubyIssues.java
new file mode 100644 (file)
index 0000000..a441ee6
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.server.issue;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import org.sonar.api.issue.IssueFinder;
+import org.sonar.api.issue.IssueQuery;
+import org.sonar.api.issue.JRubyIssues;
+import org.sonar.server.ui.JRubyFacades;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Facade of issue components for JRuby on Rails webapp
+ *
+ * @since 3.6
+ */
+public class DefaultJRubyIssues implements JRubyIssues {
+
+  private final IssueFinder finder;
+
+  public DefaultJRubyIssues(IssueFinder f) {
+    this.finder = f;
+    JRubyFacades.setIssues(this);
+  }
+
+  public IssueFinder.Results find(Map<String, Object> params, Integer currentUserId) {
+    return finder.find(newQuery(params), currentUserId);
+  }
+
+  IssueQuery newQuery(Map<String, Object> props) {
+    IssueQuery.Builder builder = IssueQuery.builder();
+    builder.keys(toStringList(props.get("keys")));
+    builder.severities(toStringList(props.get("severities")));
+    builder.statuses(toStringList(props.get("statuses")));
+    builder.resolutions(toStringList(props.get("resolutions")));
+    builder.components(toStringList(props.get("components")));
+    builder.userLogins(toStringList(props.get("userLogins")));
+    builder.assigneeLogins(toStringList(props.get("assigneeLogins")));
+    builder.limit(toInteger(props.get("limit")));
+    builder.offset(toInteger(props.get("offset")));
+    return builder.build();
+  }
+
+  List<String> toStringList(Object o) {
+    List<String> result = null;
+    if (o != null) {
+      if (o instanceof List) {
+        // assume that it contains only strings
+        result = (List) o;
+      } else if (o instanceof CharSequence) {
+        result = Lists.newArrayList(Splitter.on(',').omitEmptyStrings().split((CharSequence) o));
+      }
+    }
+    return result;
+  }
+
+  Integer toInteger(Object o) {
+    if (o instanceof Integer) {
+      return (Integer) o;
+    }
+    if (o instanceof String) {
+      return Integer.parseInt((String) o);
+    }
+    return null;
+  }
+
+  public void start() {
+    // used to force pico to instantiate the singleton at startup
+  }
+}
index 2603f435907bb2ad7249f1ebbe2f08920a1fcd78..8928ec685a89b2ac0e7c2c09e3009b425129a516 100644 (file)
@@ -75,6 +75,7 @@ import org.sonar.server.charts.ChartFactory;
 import org.sonar.server.configuration.Backup;
 import org.sonar.server.configuration.ProfilesManager;
 import org.sonar.server.database.EmbeddedDatabaseFactory;
+import org.sonar.server.issue.DefaultJRubyIssues;
 import org.sonar.server.macro.MacroInterpreter;
 import org.sonar.server.notifications.NotificationCenter;
 import org.sonar.server.notifications.NotificationService;
diff --git a/sonar-server/src/main/java/org/sonar/server/ui/DefaultJRubyIssues.java b/sonar-server/src/main/java/org/sonar/server/ui/DefaultJRubyIssues.java
deleted file mode 100644 (file)
index 2c5f371..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2012 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar 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.
- *
- * Sonar 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 Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
- */
-package org.sonar.server.ui;
-
-import com.google.common.base.Splitter;
-import com.google.common.collect.Lists;
-import org.sonar.api.issue.IssueFinder;
-import org.sonar.api.issue.IssueQuery;
-import org.sonar.api.issue.JRubyIssues;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * Facade of issue components for JRuby on Rails webapp
- *
- * @since 3.6
- */
-public class DefaultJRubyIssues implements JRubyIssues {
-
-  private final IssueFinder finder;
-
-  public DefaultJRubyIssues(IssueFinder f) {
-    this.finder = f;
-    JRubyFacades.setIssues(this);
-  }
-
-  public IssueFinder.Results find(Map<String, Object> params) {
-    return finder.find(newQuery(params));
-  }
-
-  IssueQuery newQuery(Map<String, Object> props) {
-    IssueQuery.Builder builder = IssueQuery.builder();
-    builder.keys(toStringList(props.get("keys")));
-    builder.severities(toStringList(props.get("severities")));
-    builder.statuses(toStringList(props.get("statuses")));
-    builder.resolutions(toStringList(props.get("resolutions")));
-    builder.components(toStringList(props.get("components")));
-    builder.userLogins(toStringList(props.get("userLogins")));
-    builder.assigneeLogins(toStringList(props.get("assigneeLogins")));
-    builder.limit(toInteger(props.get("limit")));
-    builder.offset(toInteger(props.get("offset")));
-    return builder.build();
-  }
-
-  List<String> toStringList(Object o) {
-    List<String> result = null;
-    if (o != null) {
-      if (o instanceof List) {
-        // assume that it contains only strings
-        result = (List) o;
-      } else if (o instanceof CharSequence) {
-        result = Lists.newArrayList(Splitter.on(',').omitEmptyStrings().split((CharSequence) o));
-      }
-    }
-    return result;
-  }
-
-  Integer toInteger(Object o) {
-    if (o instanceof Integer) {
-      return (Integer) o;
-    }
-    if (o instanceof String) {
-      return Integer.parseInt((String) o);
-    }
-    return null;
-  }
-
-  public void start() {
-    // used to force pico to instantiate the singleton at startup
-  }
-}
index 71add31103615a6cdfdc7d65a5c0a86ca921657a..23e6efbb65f000d3980c29b29c19738409b8de0a 100644 (file)
@@ -31,7 +31,7 @@ public class JRubyFacades implements ServerComponent {
 
   private static JRubyIssues issues = null;
 
-  static void setIssues(JRubyIssues i) {
+  public static void setIssues(JRubyIssues i) {
     JRubyFacades.issues = i;
   }
 
index 408ba110f372e5b2fcf0715d9f32aadb2259b750..ebeda15122e608baf20c606449e1fad07dd4837f 100644 (file)
@@ -22,16 +22,12 @@ class Api::IssuesController < Api::ApiController
 
   # GET /api/issues/search?<parameters>
   def search
-    results = find_issues(params)
+    results = Api.issues.find(params, current_user.id)
     render :json => jsonp(issues_to_json(results.issues))
   end
 
   private
 
-  def find_issues(map)
-    Api.issues.find(map)
-  end
-
   def issues_to_json(issues)
     json = []
     issues.each do |issue|
index 3b87bffa64f76539a32dd17cba7d9616faa5277f..27c0bfa50fb86fa008c1a7320d22653374b23455 100644 (file)
@@ -24,11 +24,10 @@ class IssuesController < ApplicationController
     @issues = find_issues({}).issues
   end
 
-
   protected
 
   def find_issues(map)
-    Api.issues.find(map)
+    Api.issues.find(map, current_user.id)
   end
 
 end
\ No newline at end of file
diff --git a/sonar-server/src/test/java/org/sonar/server/issue/DefaultJRubyIssuesTest.java b/sonar-server/src/test/java/org/sonar/server/issue/DefaultJRubyIssuesTest.java
new file mode 100644 (file)
index 0000000..ecfcbe7
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.server.issue;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.sonar.api.issue.IssueFinder;
+import org.sonar.api.issue.IssueQuery;
+import org.sonar.server.issue.DefaultJRubyIssues;
+
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+public class DefaultJRubyIssuesTest {
+
+  IssueFinder finder = mock(IssueFinder.class);
+  DefaultJRubyIssues facade = new DefaultJRubyIssues(finder);
+
+  @Test
+  public void test_find() throws Exception {
+    facade.find(ImmutableMap.<String, Object>of("keys", Lists.newArrayList("ABCDE")), 123);
+    verify(finder).find(argThat(new ArgumentMatcher<IssueQuery>() {
+      @Override
+      public boolean matches(Object o) {
+        return ((IssueQuery) o).keys().contains("ABCDE");
+      }
+    }), eq(123));
+  }
+
+  @Test
+  public void should_create_query_from_parameters() {
+    Map<String, Object> map = newHashMap();
+    map.put("keys", newArrayList("ABCDE1234"));
+    map.put("severities", newArrayList("MAJOR", "MINOR"));
+    map.put("statuses", newArrayList("CLOSED"));
+    map.put("resolutions", newArrayList("FALSE-POSITIVE"));
+    map.put("components", newArrayList("org.apache"));
+    map.put("userLogins", newArrayList("marilyn"));
+    map.put("assigneeLogins", newArrayList("joanna"));
+    map.put("limit", 10);
+    map.put("offset", 50);
+
+    IssueQuery query = new DefaultJRubyIssues(finder).newQuery(map);
+    assertThat(query.keys()).containsOnly("ABCDE1234");
+    assertThat(query.severities()).containsExactly("MAJOR", "MINOR");
+    assertThat(query.statuses()).containsOnly("CLOSED");
+    assertThat(query.resolutions()).containsOnly("FALSE-POSITIVE");
+    assertThat(query.userLogins()).containsOnly("marilyn");
+    assertThat(query.assigneeLogins()).containsOnly("joanna");
+    assertThat(query.limit()).isEqualTo(10);
+    assertThat(query.offset()).isEqualTo(50);
+  }
+
+  @Test
+  public void should_start() throws Exception {
+    facade.start();
+    // nothing is done
+    verifyZeroInteractions(finder);
+  }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/ui/DefaultJRubyIssuesTest.java b/sonar-server/src/test/java/org/sonar/server/ui/DefaultJRubyIssuesTest.java
deleted file mode 100644 (file)
index 0febd52..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2012 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar 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.
- *
- * Sonar 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 Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
- */
-package org.sonar.server.ui;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import org.junit.Test;
-import org.mockito.ArgumentMatcher;
-import org.sonar.api.issue.IssueFinder;
-import org.sonar.api.issue.IssueQuery;
-
-import java.util.Map;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static com.google.common.collect.Maps.newHashMap;
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-public class DefaultJRubyIssuesTest {
-
-  IssueFinder finder = mock(IssueFinder.class);
-  DefaultJRubyIssues facade = new DefaultJRubyIssues(finder);
-
-  @Test
-  public void test_find() throws Exception {
-    facade.find(ImmutableMap.<String, Object>of("keys", Lists.newArrayList("ABCDE")));
-    verify(finder).find(argThat(new ArgumentMatcher<IssueQuery>() {
-      @Override
-      public boolean matches(Object o) {
-        return ((IssueQuery) o).keys().contains("ABCDE");
-      }
-    }));
-  }
-
-  @Test
-  public void should_create_query_from_parameters() {
-    Map<String, Object> map = newHashMap();
-    map.put("keys", newArrayList("ABCDE1234"));
-    map.put("severities", newArrayList("MAJOR", "MINOR"));
-    map.put("statuses", newArrayList("CLOSED"));
-    map.put("resolutions", newArrayList("FALSE-POSITIVE"));
-    map.put("components", newArrayList("org.apache"));
-    map.put("userLogins", newArrayList("marilyn"));
-    map.put("assigneeLogins", newArrayList("joanna"));
-    map.put("limit", 10);
-    map.put("offset", 50);
-
-    IssueQuery query = new DefaultJRubyIssues(finder).newQuery(map);
-    assertThat(query.keys()).containsOnly("ABCDE1234");
-    assertThat(query.severities()).containsExactly("MAJOR", "MINOR");
-    assertThat(query.statuses()).containsOnly("CLOSED");
-    assertThat(query.resolutions()).containsOnly("FALSE-POSITIVE");
-    assertThat(query.userLogins()).containsOnly("marilyn");
-    assertThat(query.assigneeLogins()).containsOnly("joanna");
-    assertThat(query.limit()).isEqualTo(10);
-    assertThat(query.offset()).isEqualTo(50);
-  }
-
-  @Test
-  public void should_start() throws Exception {
-    facade.start();
-    // nothing is done
-    verifyZeroInteractions(finder);
-  }
-}
index ec0d68f7d9fe68aa2f58d492763de0866cb2eb0a..d02ffc79f5c2a2ae9b8c93ed1fa90092ef401f1e 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.ui;
 
 import org.junit.Test;
+import org.sonar.server.issue.DefaultJRubyIssues;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;