]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15881 Filter unauthorized portfolio projects
authorJacek <jacek.poreda@sonarsource.com>
Thu, 13 Jan 2022 09:05:41 +0000 (10:05 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 20 Jan 2022 20:02:44 +0000 (20:02 +0000)
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java
server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/TreeAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java

index 61baa640090be2c399becc5d892062d887798d09..29914eb211fca3ea2b9805d8b402276b01f16a98 100644 (file)
@@ -174,15 +174,6 @@ public abstract class AbstractUserSession implements UserSession {
     return doKeepAuthorizedProjects(permission, projects);
   }
 
-  @Override
-  public List<ComponentDto> filterAuthorizedComponents(String permission, Collection<ComponentDto> components) {
-    if (isRoot()) {
-      return new ArrayList<>(components);
-    }
-
-    return doFilterAuthorizedComponents(permission, components);
-  }
-
   /**
    * Naive implementation, to be overridden if needed
    */
@@ -203,15 +194,6 @@ public abstract class AbstractUserSession implements UserSession {
       .collect(MoreCollectors.toList());
   }
 
-  /**
-   * Naive implementation, to be overridden if needed
-   */
-  protected List<ComponentDto> doFilterAuthorizedComponents(String permission, Collection<ComponentDto> components) {
-    return components.stream()
-      .filter(c -> (PUBLIC_PERMISSIONS.contains(permission) && !c.isPrivate()) || hasComponentPermission(permission, c))
-      .collect(MoreCollectors.toList());
-  }
-
   @Override
   public UserSession checkIsRoot() {
     if (!isRoot()) {
index c61156916432c015fcc6df638809c8441f87e319..78eb7e953444d916074f5bd2dc3d89c1b94cdebf 100644 (file)
@@ -26,6 +26,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -297,24 +298,40 @@ public class ServerUserSession extends AbstractUserSession {
       Set<String> projectUuids = components.stream()
         .map(c -> defaultIfEmpty(c.getMainBranchProjectUuid(), c.projectUuid()))
         .collect(MoreCollectors.toSet(components.size()));
-      Set<String> authorizedProjectUuids = dbClient.authorizationDao().keepAuthorizedProjectUuids(dbSession, projectUuids, getUuid(), permission);
+
+      Map<String, ComponentDto> originalComponents = findComponentsByCopyComponentUuid(components,
+          dbSession);
+
+      Set<String> originalComponentsProjectUuids = originalComponents.values().stream()
+          .map(c -> defaultIfEmpty(c.getMainBranchProjectUuid(), c.projectUuid()))
+          .collect(MoreCollectors.toSet(components.size()));
+
+      Set<String> allProjectUuids = new HashSet<>(projectUuids);
+      allProjectUuids.addAll(originalComponentsProjectUuids);
+
+      Set<String> authorizedProjectUuids = dbClient.authorizationDao().keepAuthorizedProjectUuids(dbSession, allProjectUuids, getUuid(), permission);
 
       return components.stream()
-        .filter(c -> authorizedProjectUuids.contains(c.projectUuid()) || authorizedProjectUuids.contains(c.getMainBranchProjectUuid()))
+        .filter(c -> {
+          if (c.getCopyComponentUuid() != null) {
+            var componentDto = originalComponents.get(c.getCopyComponentUuid());
+            return componentDto != null && authorizedProjectUuids.contains(defaultIfEmpty(componentDto.getMainBranchProjectUuid(), componentDto.projectUuid()));
+          }
+
+          return authorizedProjectUuids.contains(c.projectUuid()) || authorizedProjectUuids.contains(
+              c.getMainBranchProjectUuid());
+        })
         .collect(MoreCollectors.toList(components.size()));
     }
   }
 
-  @Override
-  protected List<ComponentDto> doFilterAuthorizedComponents(String permission, Collection<ComponentDto> components) {
-    if (permissionsByProjectUuid == null) {
-      permissionsByProjectUuid = new HashMap<>();
-    }
-
-    return components
-      .stream()
-      .filter(x -> hasPermission(permission, defaultIfEmpty(x.getCopyComponentUuid(), x.uuid())))
-      .collect(Collectors.toList());
+  private Map<String, ComponentDto> findComponentsByCopyComponentUuid(Collection<ComponentDto> components, DbSession dbSession) {
+    Set<String> copyComponentsUuid = components.stream()
+        .map(ComponentDto::getCopyComponentUuid)
+        .filter(Objects::nonNull)
+        .collect(MoreCollectors.toSet(components.size()));
+    return dbClient.componentDao().selectByUuids(dbSession, copyComponentsUuid).stream()
+        .collect(Collectors.toMap(ComponentDto::uuid, componentDto -> componentDto));
   }
 
   @Override
index bc613ae633273ddf4a43b4d9b4ff86d69b15122c..35da6b16266dfa8488139a3c2037c2eb7b19cf05 100644 (file)
@@ -218,8 +218,4 @@ public class ThreadLocalUserSession implements UserSession {
     return get().keepAuthorizedProjects(permission, projects);
   }
 
-  @Override
-  public List<ComponentDto> filterAuthorizedComponents(String permission, Collection<ComponentDto> components) {
-    return get().filterAuthorizedComponents(permission, components);
-  }
 }
index e2d77c8d8d2aceba5cdcf79e1df17bcae64be084..5350e5785394f63a2b687b2e38c0911a78dd3fcb 100644 (file)
@@ -224,8 +224,6 @@ public interface UserSession {
 
   List<ProjectDto> keepAuthorizedProjects(String permission, Collection<ProjectDto> projects);
 
-  List<ComponentDto> filterAuthorizedComponents(String permission, Collection<ComponentDto> components);
-
   /**
    * Ensures that {@link #hasComponentPermission(String, ComponentDto)} is {@code true},
    * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
index 7c53df2434d9534847aaa252271b123aab3ba1c9..2c556e25f7b7d87a675a8dfd28a617d85c460f4c 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.user;
 
 import java.util.Arrays;
+import java.util.List;
 import javax.annotation.Nullable;
 import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
 import org.junit.Rule;
@@ -692,19 +693,6 @@ public class ServerUserSessionTest {
     assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty();
   }
 
-  @Test
-  public void keepAuthorizedComponents_filters_components_with_granted_permissions_for_logged_in_user() {
-    UserDto user = db.users().insertUser();
-    ComponentDto publicProject = db.components().insertPublicProject();
-    ComponentDto privateProject = db.components().insertPrivateProject();
-    db.users().insertProjectPermissionOnUser(user, ADMIN, privateProject);
-
-    UserSession underTest = newUserSession(user);
-
-    assertThat(underTest.keepAuthorizedComponents(ISSUE_ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty();
-    assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(privateProject, publicProject))).containsExactly(privateProject);
-  }
-
   @Test
   public void keepAuthorizedComponents_filters_components_with_granted_permissions_for_anonymous() {
     ComponentDto publicProject = db.components().insertPublicProject();
@@ -717,19 +705,6 @@ public class ServerUserSessionTest {
     assertThat(underTest.keepAuthorizedComponents(ISSUE_ADMIN, Arrays.asList(privateProject, publicProject))).containsExactly(publicProject);
   }
 
-  @Test
-  public void keepAuthorizedComponents_returns_all_specified_components_if_root() {
-    UserDto root = db.users().insertUser();
-    root = db.users().makeRoot(root);
-    ComponentDto publicProject = db.components().insertPublicProject();
-    ComponentDto privateProject = db.components().insertPrivateProject();
-
-    UserSession underTest = newUserSession(root);
-
-    assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(privateProject, publicProject)))
-      .containsExactly(privateProject, publicProject);
-  }
-
   @Test
   public void keepAuthorizedComponents_on_branches() {
     UserDto user = db.users().insertUser();
@@ -744,17 +719,7 @@ public class ServerUserSessionTest {
   }
 
   @Test
-  public void filterAuthorizedComponents_returns_empty_list_if_no_permissions_are_granted() {
-    ComponentDto publicProject = db.components().insertPublicProject();
-    ComponentDto privateProject = db.components().insertPrivateProject();
-
-    UserSession underTest = newAnonymousSession();
-
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty();
-  }
-
-  @Test
-  public void filterAuthorizedComponents_filters_components_with_granted_permissions_for_logged_in_user() {
+  public void keepAuthorizedComponents_filters_components_with_granted_permissions_for_logged_in_user() {
     ComponentDto project1 = db.components().insertPublicProject();
     ComponentDto project2 = db.components().insertPrivateProject();
     ComponentDto project3 = db.components().insertPrivateProject();
@@ -778,49 +743,49 @@ public class ServerUserSessionTest {
 
     // Add public project1 to private portfolio
     db.components().addPortfolioProject(portfolio, project1);
-    db.components().insertComponent(newProjectCopy(project1, portfolio));
+    var copyProject1 = db.components().insertComponent(newProjectCopy(project1, portfolio));
 
     // Add private project2 with USER permissions to private portfolio
     db.users().insertProjectPermissionOnUser(user, USER, project2);
     db.components().addPortfolioProject(portfolio, project2);
-    db.components().insertComponent(newProjectCopy(project2, portfolio));
+    var copyProject2 = db.components().insertComponent(newProjectCopy(project2, portfolio));
 
     // Add private project4 with USER permissions to sub-portfolio
     db.users().insertProjectPermissionOnUser(user, USER, project4);
     db.components().addPortfolioProject(subPortfolio, project4);
-    db.components().insertComponent(newProjectCopy(project4, subPortfolio));
+    var copyProject4 = db.components().insertComponent(newProjectCopy(project4, subPortfolio));
     db.components().addPortfolioReference(portfolio, subPortfolio.uuid());
 
     // Add private project3 without permissions to private portfolio
     db.components().addPortfolioProject(portfolio, project3);
-    db.components().insertComponent(newProjectCopy(project3, portfolio));
+    var copyProject3 = db.components().insertComponent(newProjectCopy(project3, portfolio));
 
     // Add private project5 with USER permissions to app
     db.users().insertProjectPermissionOnUser(user, USER, project5);
     db.components().addApplicationProject(app, project5);
-    db.components().insertComponent(newProjectCopy(project5, app));
+    var copyProject5 = db.components().insertComponent(newProjectCopy(project5, app));
     db.components().addPortfolioReference(portfolio, app.uuid());
 
     // Add private project6 to private app2
     db.components().addApplicationProject(app2, project6);
-    db.components().insertComponent(newProjectCopy(project6, app2));
+    var copyProject6 = db.components().insertComponent(newProjectCopy(project6, app2));
     db.components().addPortfolioReference(portfolio, app2.uuid());
 
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(portfolio))).isEmpty();
-    assertThat(underTest.filterAuthorizedComponents(USER, Arrays.asList(portfolio))).hasSize(1);
-    assertThat(underTest.filterAuthorizedComponents(USER, Arrays.asList(portfolio))).containsExactly(portfolio);
+    assertThat(underTest.keepAuthorizedComponents(ADMIN, List.of(portfolio))).isEmpty();
+    assertThat(underTest.keepAuthorizedComponents(USER, List.of(portfolio))).containsExactly(portfolio);
 
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(app, subPortfolio, app2))).isEmpty();
-    assertThat(underTest.filterAuthorizedComponents(USER, Arrays.asList(app, subPortfolio, app2))).hasSize(2);
-    assertThat(underTest.filterAuthorizedComponents(USER, Arrays.asList(app, subPortfolio, app2))).containsExactly(app, subPortfolio);
+    assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(app, subPortfolio, app2))).isEmpty();
+    assertThat(underTest.keepAuthorizedComponents(USER, Arrays.asList(app, subPortfolio, app2))).containsExactly(app, subPortfolio);
 
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(project1, project2, project3, project4, project5, project6))).isEmpty();
-    assertThat(underTest.filterAuthorizedComponents(USER, Arrays.asList(project1, project2, project3, project4, project5, project6))).hasSize(4);
-    assertThat(underTest.filterAuthorizedComponents(USER, Arrays.asList(project1, project2, project3, project4, project5, project6))).containsExactly(project1, project2, project4, project5);
+    assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(project1, project2, project3, project4, project5, project6))).isEmpty();
+    assertThat(underTest.keepAuthorizedComponents(USER, Arrays.asList(project1, project2, project3, project4, project5, project6))).containsExactly(project1, project2, project4, project5);
+
+    assertThat(underTest.keepAuthorizedComponents(USER, Arrays.asList(copyProject1, copyProject2, copyProject3, copyProject4, copyProject5, copyProject6)))
+        .containsExactly(copyProject1, copyProject2, copyProject4, copyProject5);
   }
 
   @Test
-  public void filterAuthorizedComponents_returns_all_specified_components_if_root() {
+  public void keepAuthorizedComponents_returns_all_specified_components_if_root() {
     UserDto root = db.users().insertUser();
     root = db.users().makeRoot(root);
     UserSession underTest = newUserSession(root);
@@ -867,76 +832,14 @@ public class ServerUserSessionTest {
     db.components().insertComponent(newProjectCopy(project6, app2));
     db.components().addPortfolioReference(portfolio, app2.uuid());
 
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(portfolio))).hasSize(1);
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(portfolio))).containsExactly(portfolio);
-
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(app, subPortfolio, app2))).hasSize(3);
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(app, subPortfolio, app2))).containsExactly(app, subPortfolio, app2);
-
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(project1, project2, project3, project4, project5, project6))).hasSize(6);
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(project1, project2, project3, project4, project5, project6))).containsExactly(project1, project2, project3, project4, project5, project6);
-  }
-
-  @Test
-  public void filterAuthorizedComponents_filters_components_with_granted_permissions_for_anonymous() {
-    UserSession underTest = newAnonymousSession();
-
-    ComponentDto project1 = db.components().insertPublicProject();
-    ComponentDto project2 = db.components().insertPrivateProject();
-    ComponentDto project3 = db.components().insertPrivateProject();
-    ComponentDto project4 = db.components().insertPrivateProject();
-    ComponentDto project5 = db.components().insertPrivateProject();
-    ComponentDto project6 = db.components().insertPrivateProject();
-
-    ComponentDto portfolio = db.components().insertPublicPortfolio();
-    db.users().insertProjectPermissionOnAnyone(ISSUE_ADMIN, portfolio);
-
-    ComponentDto subPortfolio = db.components().insertComponent(newSubPortfolio(portfolio));
-    db.users().insertProjectPermissionOnAnyone(ISSUE_ADMIN, subPortfolio);
-
-    ComponentDto app = db.components().insertPrivateApplication();
-
-    ComponentDto app2 = db.components().insertPublicApplication();
-    db.users().insertProjectPermissionOnAnyone(ISSUE_ADMIN, app2);
-
-    // Add public project1 to portfolio
-    db.components().addPortfolioProject(portfolio, project1);
-    db.components().insertComponent(newProjectCopy(project1, portfolio));
-    db.users().insertProjectPermissionOnAnyone(ISSUE_ADMIN, project1);
-
-    // Add private project2 to portfolio
-    db.components().addPortfolioProject(portfolio, project2);
-    db.components().insertComponent(newProjectCopy(project2, portfolio));
-
-    // Add private project4 to sub-portfolio
-    db.components().addPortfolioProject(subPortfolio, project4);
-    db.components().insertComponent(newProjectCopy(project4, subPortfolio));
-    db.components().addPortfolioReference(portfolio, subPortfolio.uuid());
-
-    // Add private project3 to portfolio
-    db.components().addPortfolioProject(portfolio, project3);
-    db.components().insertComponent(newProjectCopy(project3, portfolio));
-
-    // Add private project5 to app
-    db.components().addApplicationProject(app, project5);
-    db.components().insertComponent(newProjectCopy(project5, app));
-    db.components().addPortfolioReference(portfolio, app.uuid());
-
-    // Add private project6 to app2
-    db.components().addApplicationProject(app2, project6);
-    db.components().insertComponent(newProjectCopy(project6, app2));
-    db.components().addPortfolioReference(portfolio, app2.uuid());
-
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(portfolio))).isEmpty();
-    assertThat(underTest.filterAuthorizedComponents(ISSUE_ADMIN, Arrays.asList(portfolio))).hasSize(1);
+    assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(portfolio))).hasSize(1);
+    assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(portfolio))).containsExactly(portfolio);
 
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(app, subPortfolio, app2))).isEmpty();
-    assertThat(underTest.filterAuthorizedComponents(USER, Arrays.asList(app, subPortfolio, app2))).hasSize(2);
-    assertThat(underTest.filterAuthorizedComponents(USER, Arrays.asList(app, subPortfolio, app2))).containsExactly(subPortfolio, app2);
+    assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(app, subPortfolio, app2))).hasSize(3);
+    assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(app, subPortfolio, app2))).containsExactly(app, subPortfolio, app2);
 
-    assertThat(underTest.filterAuthorizedComponents(ADMIN, Arrays.asList(project1, project2, project3, project4, project5, project6))).isEmpty();
-    assertThat(underTest.filterAuthorizedComponents(USER, Arrays.asList(project1, project2, project3, project4, project5, project6))).hasSize(1);
-    assertThat(underTest.filterAuthorizedComponents(USER, Arrays.asList(project1, project2, project3, project4, project5, project6))).containsExactly(project1);
+    assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(project1, project2, project3, project4, project5, project6))).hasSize(6);
+    assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(project1, project2, project3, project4, project5, project6))).containsExactly(project1, project2, project3, project4, project5, project6);
   }
 
   @Test
index 9fdc9d6ae4eba38040a7c19104d395cbffd22c94..6046ae0bc403e3c50c1704a3b9c912a038f22291 100644 (file)
@@ -75,7 +75,6 @@ public class ThreadLocalUserSessionTest {
     assertThat(threadLocalUserSession.hasChildProjectsPermission(USER, new ProjectDto())).isFalse();
     assertThat(threadLocalUserSession.hasPortfolioChildProjectsPermission(USER, new ComponentDto())).isFalse();
     assertThat(threadLocalUserSession.hasProjectPermission(USER, new ProjectDto().getUuid())).isFalse();
-    assertThat(threadLocalUserSession.filterAuthorizedComponents(USER, Arrays.asList(new ComponentDto().setPrivate(true)))).isEmpty();
   }
 
   @Test
@@ -101,8 +100,6 @@ public class ThreadLocalUserSessionTest {
     assertThat(threadLocalUserSession.hasChildProjectsPermission(USER, new ProjectDto())).isTrue();
     assertThat(threadLocalUserSession.hasPortfolioChildProjectsPermission(USER, new ComponentDto())).isTrue();
     assertThat(threadLocalUserSession.hasProjectPermission(USER, new ProjectDto().getUuid())).isTrue();
-    assertThat(threadLocalUserSession.filterAuthorizedComponents(USER, Arrays.asList(new ComponentDto().setPrivate(true)))).hasSize(1);
-    assertThat(threadLocalUserSession.filterAuthorizedComponents(USER, Arrays.asList(new ComponentDto().setPrivate(true)))).containsExactly(new ComponentDto());
   }
 
   @Test
index c6ae4eec2146f3ff6179eee00be780d8ddf80e2a..c762d0137313178cc8179433911ab729e36f5165 100644 (file)
@@ -296,11 +296,6 @@ public class UserSessionRule implements TestRule, UserSession {
     return currentUserSession.keepAuthorizedProjects(permission, projects);
   }
 
-  @Override
-  public List<ComponentDto> filterAuthorizedComponents(String permission, Collection<ComponentDto> components) {
-    return currentUserSession.filterAuthorizedComponents(permission, components);
-  }
-
   @Override
   @CheckForNull
   public String getLogin() {
index 357bafe70ae8496363d3078848a85a3f31eaaf4c..21f58288941d20db2471718d98d8e1b612740fe4 100644 (file)
@@ -191,7 +191,7 @@ public class TreeAction implements ComponentsWsAction {
   }
 
   private List<ComponentDto> filterAuthorizedComponents(List<ComponentDto> components) {
-    return userSession.filterAuthorizedComponents(UserRole.USER, components);
+    return userSession.keepAuthorizedComponents(UserRole.USER, components);
   }
 
   private ComponentDto loadComponent(DbSession dbSession, Request request) {
index c6fc0f6501f4459ffe92a1c44fe3aca32796c28d..8b1dc32a8a7eb118bfeadbfcd710726eb0ba6f6a 100644 (file)
@@ -106,8 +106,8 @@ import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriod.snapshotToWsPeri
 import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
-import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext;
 import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter;
+import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 
 /**
@@ -576,7 +576,7 @@ public class ComponentTreeAction implements MeasuresWsAction {
   }
 
   private List<ComponentDto> filterAuthorizedComponents(List<ComponentDto> components) {
-    return userSession.filterAuthorizedComponents(UserRole.USER, components);
+    return userSession.keepAuthorizedComponents(UserRole.USER, components);
   }
 
   private static boolean componentWithMeasuresOnly(ComponentTreeRequest wsRequest) {