]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3915 Give the admin a way to send users issue reports on non favorite projects 2073/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Fri, 12 May 2017 10:37:11 +0000 (12:37 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Fri, 12 May 2017 15:48:36 +0000 (17:48 +0200)
server/sonar-db-dao/src/test/java/org/sonar/db/notification/NotificationDbTester.java
server/sonar-server/src/main/java/org/sonar/server/notification/NotificationUpdater.java
server/sonar-server/src/main/java/org/sonar/server/notification/ws/AddAction.java
server/sonar-server/src/main/java/org/sonar/server/notification/ws/ListAction.java
server/sonar-server/src/main/java/org/sonar/server/notification/ws/RemoveAction.java
server/sonar-server/src/test/java/org/sonar/server/notification/ws/AddActionTest.java
server/sonar-server/src/test/java/org/sonar/server/notification/ws/ListActionTest.java
server/sonar-server/src/test/java/org/sonar/server/notification/ws/RemoveActionTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/notification/AddRequest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/notification/NotificationsWsParameters.java
sonar-ws/src/main/java/org/sonarqube/ws/client/notification/RemoveRequest.java

index 43fbcbb14fd72f566184b38e0103f27f6926741c..778019eba87532984ad4deef0d20146aa03de2cb 100644 (file)
@@ -48,7 +48,7 @@ public class NotificationDbTester {
     List<PropertyDto> result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
       .setKey(String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel))
       .setComponentId(component == null ? null : component.getId())
-      .setUserId((int) userId)
+      .setUserId(userId)
       .build(), dbSession).stream()
       .filter(prop -> component == null ? prop.getResourceId() == null : prop.getResourceId() != null)
       .collect(MoreCollectors.toList());
@@ -60,7 +60,7 @@ public class NotificationDbTester {
     List<PropertyDto> result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
       .setKey(String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel))
       .setComponentId(component == null ? null : component.getId())
-      .setUserId((int) userId)
+      .setUserId(userId)
       .build(), dbSession);
     assertThat(result).isEmpty();
   }
index 3df6ce3c812bc15e2b82bc3a78500631fa8a598f..8ae948cc9c08416abd4a00c322273fa90e6d8a74 100644 (file)
@@ -28,7 +28,7 @@ import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.property.PropertyDto;
 import org.sonar.db.property.PropertyQuery;
-import org.sonar.server.user.UserSession;
+import org.sonar.db.user.UserDto;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
@@ -36,23 +36,16 @@ public class NotificationUpdater {
   private static final String PROP_NOTIFICATION_PREFIX = "notification";
   private static final String PROP_NOTIFICATION_VALUE = "true";
 
-  private final UserSession userSession;
   private final DbClient dbClient;
 
-  public NotificationUpdater(UserSession userSession, DbClient dbClient) {
-    this.userSession = userSession;
+  public NotificationUpdater(DbClient dbClient) {
     this.dbClient = dbClient;
   }
 
   /**
-   * Add a notification to the current authenticated user.
-   * Does nothing if the user is not authenticated.
+   * Add a notification to a user.
    */
-  public void add(DbSession dbSession, String channel, String dispatcher, @Nullable ComponentDto project) {
-    if (!userSession.isLoggedIn()) {
-      return;
-    }
-
+  public void add(DbSession dbSession, String channel, String dispatcher, UserDto user, @Nullable ComponentDto project) {
     String key = String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel);
     Long projectId = project == null ? null : project.getId();
 
@@ -60,7 +53,7 @@ public class NotificationUpdater {
       PropertyQuery.builder()
         .setKey(key)
         .setComponentId(projectId)
-        .setUserId(userSession.getUserId())
+        .setUserId(user.getId())
         .build(),
       dbSession).stream()
       .filter(notificationScope(project))
@@ -70,20 +63,15 @@ public class NotificationUpdater {
 
     dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto()
       .setKey(key)
-      .setUserId(userSession.getUserId())
+      .setUserId(user.getId())
       .setValue(PROP_NOTIFICATION_VALUE)
       .setResourceId(projectId));
   }
 
   /**
-   * Remove a notification to the current authenticated user.
-   * Does nothing if the user is not authenticated.
+   * Remove a notification from a user.
    */
-  public void remove(DbSession dbSession, String channel, String dispatcher, @Nullable ComponentDto project) {
-    if (!userSession.isLoggedIn()) {
-      return;
-    }
-
+  public void remove(DbSession dbSession, String channel, String dispatcher, UserDto user, @Nullable ComponentDto project) {
     String key = String.join(".", PROP_NOTIFICATION_PREFIX, dispatcher, channel);
     Long projectId = project == null ? null : project.getId();
 
@@ -91,7 +79,7 @@ public class NotificationUpdater {
       PropertyQuery.builder()
         .setKey(key)
         .setComponentId(projectId)
-        .setUserId(userSession.getUserId())
+        .setUserId(user.getId())
         .build(),
       dbSession).stream()
       .filter(notificationScope(project))
@@ -100,7 +88,7 @@ public class NotificationUpdater {
 
     dbClient.propertiesDao().delete(dbSession, new PropertyDto()
       .setKey(key)
-      .setUserId(userSession.getUserId())
+      .setUserId(user.getId())
       .setValue(PROP_NOTIFICATION_VALUE)
       .setResourceId(projectId));
   }
index 921387991bdecda7e2507742bbced638ce5c16a4..51685ce0c1fc73270b6d5988b5d7b24fbae77b64 100644 (file)
@@ -21,9 +21,7 @@ package org.sonar.server.notification.ws;
 
 import java.util.List;
 import java.util.Optional;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
 import org.sonar.api.notifications.NotificationChannel;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.Scopes;
@@ -33,6 +31,7 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.issue.notification.MyNewIssuesNotificationDispatcher;
 import org.sonar.server.notification.NotificationCenter;
@@ -47,9 +46,11 @@ import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.core.util.stream.MoreCollectors.toList;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
+import static org.sonar.server.ws.WsUtils.checkFound;
 import static org.sonar.server.ws.WsUtils.checkRequest;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.ACTION_ADD;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL;
+import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_LOGIN;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_TYPE;
 
@@ -76,7 +77,11 @@ public class AddAction implements NotificationsWsAction {
   public void define(WebService.NewController context) {
     WebService.NewAction action = context.createAction(ACTION_ADD)
       .setDescription("Add a notification for the authenticated user.<br>" +
-        "Requires authentication. If a project is provided, requires the 'Browse' permission on the specified project.")
+        "Requires one of the following permissions:" +
+        "<ul>" +
+        " <li>Authentication if no login is provided. If a project is provided, requires the 'Browse' permission on the specified project.</li>" +
+        " <li>System administration if a login is provided. If a project is provided, requires the 'Browse' permission on the specified project.</li>" +
+        "</ul>")
       .setSince("6.3")
       .setPost(true)
       .setHandler(this);
@@ -101,61 +106,71 @@ public class AddAction implements NotificationsWsAction {
         String.join(", ", projectDispatchers))
       .setRequired(true)
       .setExampleValue(MyNewIssuesNotificationDispatcher.KEY);
+
+    action.createParam(PARAM_LOGIN)
+      .setDescription("User login")
+      .setSince("6.4");
   }
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    Stream.of(request)
-      .map(toWsRequest())
-      .peek(checkPermissions())
-      .forEach(add());
+    AddRequest addRequest = toWsRequest(request);
+    add(addRequest);
 
     response.noContent();
   }
 
-  private Consumer<AddRequest> add() {
-    return request -> {
-      try (DbSession dbSession = dbClient.openSession(false)) {
-        Optional<ComponentDto> project = searchProject(dbSession, request);
-        notificationUpdater.add(dbSession, request.getChannel(), request.getType(), project.orElse(null));
-        dbSession.commit();
-      }
-    };
+  private void add(AddRequest request) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      checkPermissions(request);
+      UserDto user = getUser(dbSession, request);
+      ComponentDto project = searchProject(dbSession, request);
+      notificationUpdater.add(dbSession, request.getChannel(), request.getType(), user, project);
+      dbSession.commit();
+    }
+  }
+
+  private UserDto getUser(DbSession dbSession, AddRequest request) {
+    String login = request.getLogin() == null ? userSession.getLogin() : request.getLogin();
+    return checkFound(dbClient.userDao().selectByLogin(dbSession, login), "User '%s' not found", login);
   }
 
-  private Optional<ComponentDto> searchProject(DbSession dbSession, AddRequest request) {
+  @CheckForNull
+  private ComponentDto searchProject(DbSession dbSession, AddRequest request) {
     Optional<ComponentDto> project = request.getProject() == null ? empty() : Optional.of(componentFinder.getByKey(dbSession, request.getProject()));
     project.ifPresent(p -> checkRequest(Qualifiers.PROJECT.equals(p.qualifier()) && Scopes.PROJECT.equals(p.scope()),
       "Component '%s' must be a project", request.getProject()));
-    return project;
+    return project.orElse(null);
   }
 
-  private Consumer<AddRequest> checkPermissions() {
-    return request -> userSession.checkLoggedIn();
+  private void checkPermissions(AddRequest request) {
+    if (request.getLogin() == null) {
+      userSession.checkLoggedIn();
+    } else {
+      userSession.checkIsSystemAdministrator();
+    }
   }
 
-  private Function<Request, AddRequest> toWsRequest() {
-    return request -> {
-      AddRequest.Builder requestBuilder = AddRequest.builder()
-        .setType(request.mandatoryParam(PARAM_TYPE))
-        .setChannel(request.mandatoryParam(PARAM_CHANNEL));
-      String project = request.param(PARAM_PROJECT);
-      setNullable(project, requestBuilder::setProject);
-      AddRequest wsRequest = requestBuilder.build();
-
-      if (wsRequest.getProject() == null) {
-        checkRequest(globalDispatchers.contains(wsRequest.getType()), "Value of parameter '%s' (%s) must be one of: %s",
-          PARAM_TYPE,
-          wsRequest.getType(),
-          globalDispatchers);
-      } else {
-        checkRequest(projectDispatchers.contains(wsRequest.getType()), "Value of parameter '%s' (%s) must be one of: %s",
-          PARAM_TYPE,
-          wsRequest.getType(),
-          projectDispatchers);
-      }
-
-      return wsRequest;
-    };
+  private AddRequest toWsRequest(Request request) {
+    AddRequest.Builder requestBuilder = AddRequest.builder()
+      .setType(request.mandatoryParam(PARAM_TYPE))
+      .setChannel(request.mandatoryParam(PARAM_CHANNEL));
+    setNullable(request.param(PARAM_PROJECT), requestBuilder::setProject);
+    setNullable(request.param(PARAM_LOGIN), requestBuilder::setLogin);
+    AddRequest wsRequest = requestBuilder.build();
+
+    if (wsRequest.getProject() == null) {
+      checkRequest(globalDispatchers.contains(wsRequest.getType()), "Value of parameter '%s' (%s) must be one of: %s",
+        PARAM_TYPE,
+        wsRequest.getType(),
+        globalDispatchers);
+    } else {
+      checkRequest(projectDispatchers.contains(wsRequest.getType()), "Value of parameter '%s' (%s) must be one of: %s",
+        PARAM_TYPE,
+        wsRequest.getType(),
+        projectDispatchers);
+    }
+
+    return wsRequest;
   }
 }
index a24d4c8a37abe08fdc67f00bee6cb00ab8eaf35d..b1b634b389c6ae2ce07f5428e06ee3204a0569c2 100644 (file)
@@ -25,7 +25,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.UnaryOperator;
@@ -42,6 +41,7 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.property.PropertyDto;
 import org.sonar.db.property.PropertyQuery;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.notification.NotificationCenter;
 import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.Notifications.ListResponse;
@@ -55,8 +55,10 @@ import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.core.util.stream.MoreCollectors.toOneElement;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
+import static org.sonar.server.ws.WsUtils.checkFound;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.ACTION_LIST;
+import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_LOGIN;
 
 public class ListAction implements NotificationsWsAction {
   private static final Splitter PROPERTY_KEY_SPLITTER = Splitter.on(".");
@@ -77,42 +79,53 @@ public class ListAction implements NotificationsWsAction {
 
   @Override
   public void define(WebService.NewController context) {
-    context.createAction(ACTION_LIST)
+    WebService.NewAction action = context.createAction(ACTION_LIST)
       .setDescription("List notifications of the authenticated user.<br>" +
-        "Requires authentication.")
+        "Requires one of the following permissions:" +
+        "<ul>" +
+        "  <li>Authentication if no login is provided</li>" +
+        "  <li>System administration if a login is provided</li>" +
+        "</ul>")
       .setSince("6.3")
       .setResponseExample(getClass().getResource("list-example.json"))
       .setHandler(this);
+
+    action.createParam(PARAM_LOGIN)
+      .setDescription("User login")
+      .setSince("6.4");
   }
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    ListResponse listResponse = Stream.of(request)
-      .peek(checkPermissions())
-      .map(search())
-      .collect(toOneElement());
+    ListResponse listResponse = search(request);
 
     writeProtobuf(listResponse, request, response);
   }
 
-  private Function<Request, ListResponse> search() {
-    return request -> {
-      try (DbSession dbSession = dbClient.openSession(false)) {
-        return Stream
-          .of(ListResponse.newBuilder())
-          .map(r -> r.addAllChannels(channels))
-          .map(r -> r.addAllGlobalTypes(globalDispatchers))
-          .map(r -> r.addAllPerProjectTypes(perProjectDispatchers))
-          .map(addNotifications(dbSession))
-          .map(ListResponse.Builder::build)
-          .collect(toOneElement());
-      }
-    };
+  private ListResponse search(Request request) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      checkPermissions(request);
+      UserDto user = getUser(dbSession, request);
+
+      return Stream
+        .of(ListResponse.newBuilder())
+        .map(r -> r.addAllChannels(channels))
+        .map(r -> r.addAllGlobalTypes(globalDispatchers))
+        .map(r -> r.addAllPerProjectTypes(perProjectDispatchers))
+        .map(addNotifications(dbSession, user))
+        .map(ListResponse.Builder::build)
+        .collect(toOneElement());
+    }
+  }
+
+  private UserDto getUser(DbSession dbSession, Request request) {
+    String login = request.param(PARAM_LOGIN) == null ? userSession.getLogin() : request.param(PARAM_LOGIN);
+    return checkFound(dbClient.userDao().selectByLogin(dbSession, login), "User '%s' not found", login);
   }
 
-  private UnaryOperator<ListResponse.Builder> addNotifications(DbSession dbSession) {
+  private UnaryOperator<ListResponse.Builder> addNotifications(DbSession dbSession, UserDto user) {
     return response -> {
-      List<PropertyDto> properties = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setUserId(userSession.getUserId()).build(), dbSession);
+      List<PropertyDto> properties = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setUserId(user.getId()).build(), dbSession);
       Map<Long, ComponentDto> componentsById = searchProjects(dbSession, properties);
       Map<String, OrganizationDto> organizationsByUuid = getOrganizations(dbSession, componentsById.values());
 
@@ -195,7 +208,11 @@ public class ListAction implements NotificationsWsAction {
       .setProjectName(project.name());
   }
 
-  private Consumer<Request> checkPermissions() {
-    return request -> userSession.checkLoggedIn();
+  private void checkPermissions(Request request) {
+    if (request.param(PARAM_LOGIN) == null) {
+      userSession.checkLoggedIn();
+    } else {
+      userSession.checkIsSystemAdministrator();
+    }
   }
 }
index 573f2a7df626f5a2b34161b7ba6f6edd0b8af291..e44841a5bde3c88384a5e69ff8de5bfe2911338d 100644 (file)
@@ -21,10 +21,7 @@ package org.sonar.server.notification.ws;
 
 import java.util.List;
 import java.util.Optional;
-import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 import org.sonar.api.notifications.NotificationChannel;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.Scopes;
@@ -34,6 +31,7 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.issue.notification.MyNewIssuesNotificationDispatcher;
 import org.sonar.server.notification.NotificationCenter;
@@ -47,9 +45,11 @@ import static java.util.Optional.empty;
 import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
+import static org.sonar.server.ws.WsUtils.checkFound;
 import static org.sonar.server.ws.WsUtils.checkRequest;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.ACTION_REMOVE;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL;
+import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_LOGIN;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_TYPE;
 
@@ -76,7 +76,11 @@ public class RemoveAction implements NotificationsWsAction {
   public void define(WebService.NewController context) {
     WebService.NewAction action = context.createAction(ACTION_REMOVE)
       .setDescription("Remove a notification for the authenticated user.<br>" +
-        "Requires authentication. If a project is provided, requires the 'Browse' permission on the specified project.")
+        "Requires one of the following permissions:" +
+        "<ul>" +
+        "  <li>Authentication if no login is provided</li>" +
+        "  <li>System administration if a login is provided</li>" +
+        "</ul>")
       .setSince("6.3")
       .setPost(true)
       .setHandler(this);
@@ -101,26 +105,33 @@ public class RemoveAction implements NotificationsWsAction {
         projectDispatchers.stream().sorted().collect(Collectors.joining(", ")))
       .setRequired(true)
       .setExampleValue(MyNewIssuesNotificationDispatcher.KEY);
+
+    action.createParam(PARAM_LOGIN)
+      .setDescription("User login")
+      .setSince("6.4");
   }
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    Stream.of(request)
-      .map(toWsRequest())
-      .peek(checkPermissions())
-      .forEach(remove());
+    RemoveRequest removeRequest = toWsRequest(request);
+    remove(removeRequest);
 
     response.noContent();
   }
 
-  private Consumer<RemoveRequest> remove() {
-    return request -> {
-      try (DbSession dbSession = dbClient.openSession(false)) {
-        Optional<ComponentDto> project = searchProject(dbSession, request);
-        notificationUpdater.remove(dbSession, request.getChannel(), request.getType(), project.orElse(null));
-        dbSession.commit();
-      }
-    };
+  private void remove(RemoveRequest request) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      checkPermissions(request);
+      UserDto user = getUser(dbSession, request);
+      Optional<ComponentDto> project = searchProject(dbSession, request);
+      notificationUpdater.remove(dbSession, request.getChannel(), request.getType(), user, project.orElse(null));
+      dbSession.commit();
+    }
+  }
+
+  private UserDto getUser(DbSession dbSession, RemoveRequest request) {
+    String login = request.getLogin() == null ? userSession.getLogin() : request.getLogin();
+    return checkFound(dbClient.userDao().selectByLogin(dbSession, login), "User '%s' not found", login);
   }
 
   private Optional<ComponentDto> searchProject(DbSession dbSession, RemoveRequest request) {
@@ -130,32 +141,34 @@ public class RemoveAction implements NotificationsWsAction {
     return project;
   }
 
-  private Consumer<RemoveRequest> checkPermissions() {
-    return request -> userSession.checkLoggedIn();
+  private void checkPermissions(RemoveRequest request) {
+    if (request.getLogin() == null) {
+      userSession.checkLoggedIn();
+    } else {
+      userSession.checkIsSystemAdministrator();
+    }
   }
 
-  private Function<Request, RemoveRequest> toWsRequest() {
-    return request -> {
-      RemoveRequest.Builder requestBuilder = RemoveRequest.builder()
-        .setType(request.mandatoryParam(PARAM_TYPE))
-        .setChannel(request.mandatoryParam(PARAM_CHANNEL));
-      String project = request.param(PARAM_PROJECT);
-      setNullable(project, requestBuilder::setProject);
-      RemoveRequest wsRequest = requestBuilder.build();
-
-      if (wsRequest.getProject() == null) {
-        checkRequest(globalDispatchers.contains(wsRequest.getType()), "Value of parameter '%s' (%s) must be one of: %s",
-          PARAM_TYPE,
-          wsRequest.getType(),
-          globalDispatchers);
-      } else {
-        checkRequest(projectDispatchers.contains(wsRequest.getType()), "Value of parameter '%s' (%s) must be one of: %s",
-          PARAM_TYPE,
-          wsRequest.getType(),
-          projectDispatchers);
-      }
-
-      return wsRequest;
-    };
+  private RemoveRequest toWsRequest(Request request) {
+    RemoveRequest.Builder requestBuilder = RemoveRequest.builder()
+      .setType(request.mandatoryParam(PARAM_TYPE))
+      .setChannel(request.mandatoryParam(PARAM_CHANNEL));
+    setNullable(request.param(PARAM_PROJECT), requestBuilder::setProject);
+    setNullable(request.param(PARAM_LOGIN), requestBuilder::setLogin);
+    RemoveRequest wsRequest = requestBuilder.build();
+
+    if (wsRequest.getProject() == null) {
+      checkRequest(globalDispatchers.contains(wsRequest.getType()), "Value of parameter '%s' (%s) must be one of: %s",
+        PARAM_TYPE,
+        wsRequest.getType(),
+        globalDispatchers);
+    } else {
+      checkRequest(projectDispatchers.contains(wsRequest.getType()), "Value of parameter '%s' (%s) must be one of: %s",
+        PARAM_TYPE,
+        wsRequest.getType(),
+        projectDispatchers);
+    }
+
+    return wsRequest;
   }
 }
index 99b06b03cfae0ddfb436a510c74b252c96343646..3cc9e0de99f7614c2e9719fd7df6c7513f79c7f7 100644 (file)
@@ -28,8 +28,10 @@ import org.sonar.api.notifications.NotificationChannel;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.notification.NotificationCenter;
@@ -48,6 +50,7 @@ import static org.sonar.db.component.ComponentTesting.newView;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL;
+import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_LOGIN;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_TYPE;
 
@@ -55,10 +58,11 @@ public class AddActionTest {
   private static final String NOTIF_MY_NEW_ISSUES = "Dispatcher1";
   private static final String NOTIF_NEW_ISSUES = "Dispatcher2";
   private static final String NOTIF_NEW_QUALITY_GATE_STATUS = "Dispatcher3";
+  private static final String USER_LOGIN = "george.orwell";
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
   @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone().logIn().setUserId(123);
+  public UserSessionRule userSession;
   @Rule
   public DbTester db = DbTester.create();
   private DbClient dbClient = db.getDbClient();
@@ -68,6 +72,8 @@ public class AddActionTest {
   // default channel, based on class simple name
   private NotificationChannel defaultChannel = new FakeNotificationChannel("EmailNotificationChannel");
 
+  private UserDto user;
+
   private NotificationCenter notificationCenter;
   private AddAction underTest;
   private WsActionTester ws;
@@ -86,10 +92,13 @@ public class AddActionTest {
       .setProperty(GLOBAL_NOTIFICATION, "true")
       .setProperty(PER_PROJECT_NOTIFICATION, "true");
 
+    user = db.users().insertUser(USER_LOGIN);
+    userSession = UserSessionRule.standalone().logIn(user);
+
     notificationCenter = new NotificationCenter(
       new NotificationDispatcherMetadata[] {metadata1, metadata2, metadata3},
       new NotificationChannel[] {emailChannel, twitterChannel, defaultChannel});
-    underTest = new AddAction(notificationCenter, new NotificationUpdater(userSession, dbClient), dbClient, new ComponentFinder(dbClient), userSession);
+    underTest = new AddAction(notificationCenter, new NotificationUpdater(dbClient), dbClient, new ComponentFinder(dbClient), userSession);
     ws = new WsActionTester(underTest);
   }
 
@@ -145,6 +154,34 @@ public class AddActionTest {
     assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT);
   }
 
+  @Test
+  public void add_a_notification_to_a_user_as_system_administrator() {
+    userSession.logIn().setSystemAdministrator();
+
+    call(request.setLogin(user.getLogin()));
+
+    db.notifications().assertExists(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user.getId(), null);
+  }
+
+  @Test
+  public void fail_if_login_is_provided_and_unknown() {
+    userSession.logIn().setSystemAdministrator();
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("User 'LOGIN 404' not found");
+
+    call(request.setLogin("LOGIN 404"));
+  }
+
+  @Test
+  public void fail_if_login_provided_and_not_system_administrator() {
+    userSession.logIn().setNonSystemAdministrator();
+
+    expectedException.expect(ForbiddenException.class);
+
+    call(request.setLogin(user.getLogin()));
+  }
+
   @Test
   public void fail_when_notification_already_exists() {
     call(request);
@@ -219,6 +256,7 @@ public class AddActionTest {
     request.setParam(PARAM_TYPE, wsRequest.getType());
     setNullable(wsRequest.getChannel(), channel -> request.setParam(PARAM_CHANNEL, channel));
     setNullable(wsRequest.getProject(), project -> request.setParam(PARAM_PROJECT, project));
+    setNullable(wsRequest.getLogin(), login -> request.setParam(PARAM_LOGIN, login));
     return request.execute();
   }
 
index 83bbe48a3ab71394f77302333acbbf4dfde4d5d1..7707606d715bd233cf9ffcad19d2cb3441dcb2cd 100644 (file)
@@ -33,6 +33,9 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.permission.UserPermissionDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.notification.NotificationCenter;
 import org.sonar.server.notification.NotificationDispatcherMetadata;
@@ -57,7 +60,7 @@ public class ListActionTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
   @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone().logIn().setUserId(123);
+  public UserSessionRule userSession;
   @Rule
   public DbTester db = DbTester.create();
 
@@ -66,6 +69,7 @@ public class ListActionTest {
 
   private NotificationChannel emailChannel = new FakeNotificationChannel("EmailChannel");
   private NotificationChannel twitterChannel = new FakeNotificationChannel("TwitterChannel");
+  private UserDto user;
 
   private NotificationUpdater notificationUpdater;
 
@@ -82,10 +86,13 @@ public class ListActionTest {
       .setProperty(GLOBAL_NOTIFICATION, "true")
       .setProperty(PER_PROJECT_NOTIFICATION, "true");
 
+    user = db.users().insertUser();
+    userSession = UserSessionRule.standalone().logIn(user);
+
     NotificationCenter notificationCenter = new NotificationCenter(
       new NotificationDispatcherMetadata[] {metadata1, metadata2, metadata3},
       new NotificationChannel[] {emailChannel, twitterChannel});
-    notificationUpdater = new NotificationUpdater(userSession, dbClient);
+    notificationUpdater = new NotificationUpdater(dbClient);
     ListAction underTest = new ListAction(notificationCenter, dbClient, userSession);
     ws = new WsActionTester(underTest);
   }
@@ -115,8 +122,8 @@ public class ListActionTest {
   public void filter_unauthorized_projects() {
     ComponentDto project = addComponent(ComponentTesting.newPrivateProjectDto(db.organizations().insert()).setKey("K1"));
     ComponentDto anotherProject = db.components().insertPrivateProject();
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, project);
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, anotherProject);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, project);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, anotherProject);
     dbSession.commit();
 
     ListResponse result = call();
@@ -126,8 +133,8 @@ public class ListActionTest {
 
   @Test
   public void filter_channels() {
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, null);
-    notificationUpdater.add(dbSession, "Unknown Channel", NOTIF_MY_NEW_ISSUES, null);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
+    notificationUpdater.add(dbSession, "Unknown Channel", NOTIF_MY_NEW_ISSUES, user, null);
     dbSession.commit();
 
     ListResponse result = call();
@@ -137,8 +144,8 @@ public class ListActionTest {
 
   @Test
   public void filter_overall_dispatchers() {
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, null);
-    notificationUpdater.add(dbSession, emailChannel.getKey(), "Unknown Notification", null);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), "Unknown Notification", user, null);
     dbSession.commit();
 
     ListResponse result = call();
@@ -149,8 +156,8 @@ public class ListActionTest {
   @Test
   public void filter_per_project_dispatchers() {
     ComponentDto project = addComponent(ComponentTesting.newPrivateProjectDto(db.organizations().insert()).setKey("K1"));
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, project);
-    notificationUpdater.add(dbSession, emailChannel.getKey(), "Unknown Notification", project);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, project);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), "Unknown Notification", user, project);
     dbSession.commit();
 
     ListResponse result = call();
@@ -164,12 +171,12 @@ public class ListActionTest {
   public void order_with_global_then_by_channel_and_dispatcher() {
     OrganizationDto organization = db.organizations().insert();
     ComponentDto project = addComponent(ComponentTesting.newPrivateProjectDto(organization).setKey("K1"));
-    notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_MY_NEW_ISSUES, null);
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, null);
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_NEW_ISSUES, null);
-    notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_MY_NEW_ISSUES, project);
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, project);
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, project);
+    notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_NEW_ISSUES, user, null);
+    notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, project);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, project);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, user, project);
     dbSession.commit();
 
     ListResponse result = call();
@@ -185,16 +192,52 @@ public class ListActionTest {
         tuple(twitterChannel.getKey(), organization.getKey(), NOTIF_MY_NEW_ISSUES, "K1"));
   }
 
+  @Test
+  public void list_user_notifications_as_system_admin() {
+    userSession.logIn().setSystemAdministrator();
+
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_NEW_ISSUES, user, null);
+    dbSession.commit();
+
+    ListResponse result = call(user.getLogin());
+
+    assertThat(result.getNotificationsList())
+      .extracting(Notification::getType)
+      .containsOnly(NOTIF_MY_NEW_ISSUES, NOTIF_NEW_ISSUES);
+  }
+
+  @Test
+  public void fail_if_login_and_not_system_admin() {
+    userSession.logIn().setNonSystemAdministrator();
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
+    dbSession.commit();
+
+    expectedException.expect(ForbiddenException.class);
+
+    call(user.getLogin());
+  }
+
+  @Test
+  public void fail_if_login_is_provided_and_unknown() {
+    userSession.logIn().setSystemAdministrator();
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("User 'LOGIN 404' not found");
+
+    call("LOGIN 404");
+  }
+
   @Test
   public void json_example() {
     OrganizationDto organization = db.organizations().insertForKey("my-org-1");
     ComponentDto project = addComponent(ComponentTesting.newPrivateProjectDto(organization).setKey(KEY_PROJECT_EXAMPLE_001).setName("My Project"));
-    notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_MY_NEW_ISSUES, null);
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, null);
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_NEW_ISSUES, null);
-    notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_MY_NEW_ISSUES, project);
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, project);
-    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, project);
+    notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_NEW_ISSUES, user, null);
+    notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, project);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, project);
+    notificationUpdater.add(dbSession, emailChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, user, project);
     dbSession.commit();
 
     String result = ws.newRequest().execute().getInput();
@@ -210,8 +253,13 @@ public class ListActionTest {
 
     assertThat(definition.key()).isEqualTo("list");
     assertThat(definition.isPost()).isFalse();
-    assertThat(definition.params()).isEmpty();
+    assertThat(definition.since()).isEqualTo("6.3");
     assertThat(definition.responseExampleAsString()).isNotEmpty();
+    assertThat(definition.params()).hasSize(1);
+
+    WebService.Param loginParam = definition.param("login");
+    assertThat(loginParam.since()).isEqualTo("6.4");
+    assertThat(loginParam.isRequired()).isFalse();
   }
 
   @Test
@@ -227,9 +275,13 @@ public class ListActionTest {
     return ws.newRequest().executeProtobuf(ListResponse.class);
   }
 
+  private ListResponse call(String login) {
+    return ws.newRequest().setParam("login", login).executeProtobuf(ListResponse.class);
+  }
+
   private ComponentDto addComponent(ComponentDto component) {
     db.components().insertComponent(component);
-    dbClient.userPermissionDao().insert(dbSession, new UserPermissionDto(component.getOrganizationUuid(), UserRole.USER, userSession.getUserId(), component.getId()));
+    dbClient.userPermissionDao().insert(dbSession, new UserPermissionDto(component.getOrganizationUuid(), UserRole.USER, user.getId(), component.getId()));
     db.commit();
 
     return component;
index c467a1cc2af42fe34a80c223bf26e7c388d07795..8cc3f5ccf87f5300cc452cd28a136a2ec075f8af 100644 (file)
@@ -29,8 +29,10 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.notification.NotificationCenter;
@@ -40,7 +42,7 @@ import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsActionTester;
-import org.sonarqube.ws.client.notification.AddRequest;
+import org.sonarqube.ws.client.notification.RemoveRequest;
 
 import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -49,6 +51,7 @@ import static org.sonar.db.component.ComponentTesting.newView;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
 import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_CHANNEL;
+import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_LOGIN;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_PROJECT;
 import static org.sonarqube.ws.client.notification.NotificationsWsParameters.PARAM_TYPE;
 
@@ -59,7 +62,7 @@ public class RemoveActionTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
   @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone().logIn().setUserId(123);
+  public UserSessionRule userSession;
   @Rule
   public DbTester db = DbTester.create();
   private DbClient dbClient = db.getDbClient();
@@ -75,11 +78,15 @@ public class RemoveActionTest {
   private RemoveAction underTest;
 
   private WsActionTester ws;
-  private AddRequest.Builder request = AddRequest.builder()
-    .setType(NOTIF_MY_NEW_ISSUES);
+  private RemoveRequest.Builder request = RemoveRequest.builder().setType(NOTIF_MY_NEW_ISSUES);
+
+  private UserDto user;
 
   @Before
   public void setUp() {
+    user = db.users().insertUser();
+    userSession = UserSessionRule.standalone().logIn(user);
+
     NotificationDispatcherMetadata metadata1 = NotificationDispatcherMetadata.create(NOTIF_MY_NEW_ISSUES)
       .setProperty(GLOBAL_NOTIFICATION, "true")
       .setProperty(PER_PROJECT_NOTIFICATION, "true");
@@ -92,46 +99,46 @@ public class RemoveActionTest {
     notificationCenter = new NotificationCenter(
       new NotificationDispatcherMetadata[] {metadata1, metadata2, metadata3},
       new NotificationChannel[] {emailChannel, twitterChannel, defaultChannel});
-    notificationUpdater = new NotificationUpdater(userSession, dbClient);
+    notificationUpdater = new NotificationUpdater(dbClient);
     underTest = new RemoveAction(notificationCenter, notificationUpdater, dbClient, new ComponentFinder(dbClient), userSession);
     ws = new WsActionTester(underTest);
   }
 
   @Test
   public void remove_to_email_channel_by_default() {
-    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, null);
+    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
     dbSession.commit();
 
     call(request);
 
-    db.notifications().assertDoesNotExist(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, userSession.getUserId(), null);
+    db.notifications().assertDoesNotExist(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user.getId(), null);
   }
 
   @Test
   public void remove_from_a_specific_channel() {
-    notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, null);
+    notificationUpdater.add(dbSession, twitterChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, user, null);
     dbSession.commit();
 
     call(request.setType(NOTIF_NEW_QUALITY_GATE_STATUS).setChannel(twitterChannel.getKey()));
 
-    db.notifications().assertDoesNotExist(twitterChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, userSession.getUserId(), null);
+    db.notifications().assertDoesNotExist(twitterChannel.getKey(), NOTIF_NEW_QUALITY_GATE_STATUS, user.getId(), null);
   }
 
   @Test
   public void remove_a_project_notification() {
     ComponentDto project = db.components().insertPrivateProject();
-    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, project);
+    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, project);
     dbSession.commit();
 
     call(request.setProject(project.getKey()));
 
-    db.notifications().assertDoesNotExist(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, userSession.getUserId(), project);
+    db.notifications().assertDoesNotExist(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user.getId(), project);
   }
 
   @Test
   public void fail_when_remove_a_global_notification_when_a_project_one_exists() {
     ComponentDto project = db.components().insertPrivateProject();
-    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, project);
+    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, project);
     dbSession.commit();
 
     expectedException.expect(IllegalArgumentException.class);
@@ -143,7 +150,7 @@ public class RemoveActionTest {
   @Test
   public void fail_when_remove_a_project_notification_when_a_global_one_exists() {
     ComponentDto project = db.components().insertPrivateProject();
-    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, null);
+    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
     dbSession.commit();
 
     expectedException.expect(IllegalArgumentException.class);
@@ -154,7 +161,7 @@ public class RemoveActionTest {
 
   @Test
   public void http_no_content() {
-    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, null);
+    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
     dbSession.commit();
 
     TestResponse result = call(request);
@@ -162,6 +169,39 @@ public class RemoveActionTest {
     assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT);
   }
 
+  @Test
+  public void remove_a_notification_from_a_user_as_system_administrator() {
+    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
+    db.notifications().assertExists(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user.getId(), null);
+    userSession.logIn().setSystemAdministrator();
+    dbSession.commit();
+
+    call(request.setLogin(user.getLogin()));
+
+    db.notifications().assertDoesNotExist(defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user.getId(), null);
+  }
+
+  @Test
+  public void fail_if_login_is_provided_and_unknown() {
+    userSession.logIn().setSystemAdministrator();
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("User 'LOGIN 404' not found");
+
+    call(request.setLogin("LOGIN 404"));
+  }
+
+  @Test
+  public void fail_if_login_provided_and_not_system_administrator() {
+    userSession.logIn().setNonSystemAdministrator();
+    notificationUpdater.add(dbSession, defaultChannel.getKey(), NOTIF_MY_NEW_ISSUES, user, null);
+    dbSession.commit();
+
+    expectedException.expect(ForbiddenException.class);
+
+    call(request.setLogin(user.getLogin()));
+  }
+
   @Test
   public void fail_when_notification_does_not_exist() {
     expectedException.expect(IllegalArgumentException.class);
@@ -228,12 +268,14 @@ public class RemoveActionTest {
     call(request);
   }
 
-  private TestResponse call(AddRequest.Builder wsRequestBuilder) {
-    AddRequest wsRequest = wsRequestBuilder.build();
+  private TestResponse call(RemoveRequest.Builder wsRequestBuilder) {
+    RemoveRequest wsRequest = wsRequestBuilder.build();
+
     TestRequest request = ws.newRequest();
     request.setParam(PARAM_TYPE, wsRequest.getType());
     setNullable(wsRequest.getChannel(), channel -> request.setParam(PARAM_CHANNEL, channel));
     setNullable(wsRequest.getProject(), project -> request.setParam(PARAM_PROJECT, project));
+    setNullable(wsRequest.getLogin(), login -> request.setParam(PARAM_LOGIN, login));
     return request.execute();
   }
 
index 22999af106f9046fe14e423444997ded137ae6c3..ff509a255229672c11a46afc1d641582d481182e 100644 (file)
@@ -28,11 +28,13 @@ public class AddRequest {
   private final String type;
   private final String channel;
   private final String project;
+  private final String login;
 
   private AddRequest(Builder builder) {
     this.channel = builder.channel;
     this.type = builder.type;
     this.project = builder.project;
+    this.login = builder.login;
   }
 
   public String getType() {
@@ -49,6 +51,11 @@ public class AddRequest {
     return project;
   }
 
+  @CheckForNull
+  public String getLogin() {
+    return login;
+  }
+
   public static Builder builder() {
     return new Builder();
   }
@@ -57,6 +64,7 @@ public class AddRequest {
     private String type;
     private String channel;
     private String project;
+    private String login;
 
     private Builder() {
       // enforce factory method
@@ -77,6 +85,11 @@ public class AddRequest {
       return this;
     }
 
+    public Builder setLogin(@Nullable String login) {
+      this.login = login;
+      return this;
+    }
+
     public AddRequest build() {
       requireNonNull(type, "Notification is required");
       return new AddRequest(this);
index c92ca2b24b863674915080e45bc4b53561916c8a..40fe2541890736379229b417017d172c262d8a3d 100644 (file)
@@ -28,6 +28,7 @@ public class NotificationsWsParameters {
   public static final String PARAM_PROJECT = "project";
   public static final String PARAM_CHANNEL = "channel";
   public static final String PARAM_TYPE = "type";
+  public static final String PARAM_LOGIN = "login";
 
   private NotificationsWsParameters() {
     // prevent instantiation
index 9b1b3790982c748d069a3a3766bfd0fce8504b61..91547b5880545e33b7a0010fdad3d1a907ee6d7b 100644 (file)
@@ -28,11 +28,13 @@ public class RemoveRequest {
   private final String type;
   private final String channel;
   private final String project;
+  private final String login;
 
   private RemoveRequest(Builder builder) {
     this.channel = builder.channel;
     this.type = builder.type;
     this.project = builder.project;
+    this.login = builder.login;
   }
 
   public String getType() {
@@ -49,6 +51,11 @@ public class RemoveRequest {
     return project;
   }
 
+  @CheckForNull
+  public String getLogin() {
+    return login;
+  }
+
   public static Builder builder() {
     return new Builder();
   }
@@ -57,6 +64,7 @@ public class RemoveRequest {
     private String type;
     private String channel;
     private String project;
+    private String login;
 
     private Builder() {
       // enforce factory method
@@ -77,6 +85,11 @@ public class RemoveRequest {
       return this;
     }
 
+    public Builder setLogin(@Nullable String login) {
+      this.login = login;
+      return this;
+    }
+
     public RemoveRequest build() {
       requireNonNull(type, "Notification is required");
       return new RemoveRequest(this);