]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18396 - Support user soft deletion on Azure through SCIM
authorAntoine Vigneau <antoine.vigneau@sonarsource.com>
Wed, 8 Feb 2023 09:39:00 +0000 (10:39 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 10 Feb 2023 20:02:45 +0000 (20:02 +0000)
server/sonar-webserver/src/main/java/org/sonar/server/platform/web/SecurityServletFilter.java
sonar-ws/src/main/java/org/sonarqube/ws/client/BaseRequest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java
sonar-ws/src/main/java/org/sonarqube/ws/client/PatchRequest.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/PostRequest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/RequestWithPayload.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/WsRequest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/users/SearchRequest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java
sonar-ws/src/test/java/org/sonarqube/ws/client/ServiceTester.java

index 93fb0aadf574e3f606c3bfee30ed820feeba97f2..83522dc7c142f60df2bd9c0b738bfc6cf07bbd3c 100644 (file)
@@ -35,7 +35,7 @@ import javax.servlet.http.HttpServletResponse;
  */
 public class SecurityServletFilter implements Filter {
 
-  private static final Set<String> ALLOWED_HTTP_METHODS = Set.of("DELETE", "GET", "HEAD", "POST", "PUT");
+  private static final Set<String> ALLOWED_HTTP_METHODS = Set.of("DELETE", "GET", "HEAD", "POST", "PUT", "PATCH");
 
   @Override
   public void init(FilterConfig filterConfig) {
index afd4faf870c3cb2554444c2cd85a28d48026085e..6442a6d187d3706cbb37d2cab92a2989c785937a 100644 (file)
@@ -41,7 +41,7 @@ import static java.util.Objects.requireNonNull;
 import static org.sonarqube.ws.WsUtils.checkArgument;
 import static org.sonarqube.ws.WsUtils.isNullOrEmpty;
 
-abstract class BaseRequest<SELF extends BaseRequest> implements WsRequest {
+abstract class BaseRequest<SELF extends BaseRequest<SELF>> implements WsRequest {
 
   private final String path;
 
@@ -51,7 +51,7 @@ abstract class BaseRequest<SELF extends BaseRequest> implements WsRequest {
   private final DefaultHeaders headers = new DefaultHeaders();
   private OptionalInt timeOutInMs = OptionalInt.empty();
   private OptionalInt writeTimeOutInMs = OptionalInt.empty();
-  
+
   BaseRequest(String path) {
     this.path = path;
   }
@@ -71,9 +71,9 @@ abstract class BaseRequest<SELF extends BaseRequest> implements WsRequest {
     return timeOutInMs;
   }
 
-  public SELF setTimeOutInMs(int timeOutInMs) {
+  public <T extends SELF> T setTimeOutInMs(int timeOutInMs) {
     this.timeOutInMs = OptionalInt.of(timeOutInMs);
-    return (SELF) this;
+    return (T) this;
   }
 
   @Override
@@ -81,57 +81,57 @@ abstract class BaseRequest<SELF extends BaseRequest> implements WsRequest {
     return writeTimeOutInMs;
   }
 
-  public SELF setWriteTimeOutInMs(int writeTimeOutInMs) {
+  public <T extends SELF> T setWriteTimeOutInMs(int writeTimeOutInMs) {
     this.writeTimeOutInMs = OptionalInt.of(writeTimeOutInMs);
-    return (SELF) this;
+    return (T) this;
   }
 
   /**
    * Expected media type of response. Default is {@link MediaTypes#JSON}.
    */
   @SuppressWarnings("unchecked")
-  public SELF setMediaType(String s) {
+  public  <T extends SELF> T  setMediaType(String s) {
     requireNonNull(s, "media type of response cannot be null");
     this.mediaType = s;
-    return (SELF) this;
+    return (T) this;
   }
 
-  public SELF setParam(String key, @Nullable String value) {
-    return setSingleValueParam(key, value);
+  public <T extends SELF> T setParam(String key, @Nullable String value) {
+    return (T) setSingleValueParam(key, value);
   }
 
-  public SELF setParam(String key, @Nullable Integer value) {
+  public <T extends SELF> T setParam(String key, @Nullable Integer value) {
     return setSingleValueParam(key, value);
   }
 
-  public SELF setParam(String key, @Nullable Long value) {
+  public <T extends SELF> T setParam(String key, @Nullable Long value) {
     return setSingleValueParam(key, value);
   }
 
-  public SELF setParam(String key, @Nullable Float value) {
+  public <T extends SELF> T setParam(String key, @Nullable Float value) {
     return setSingleValueParam(key, value);
   }
 
-  public SELF setParam(String key, @Nullable Boolean value) {
+  public <T extends SELF> T setParam(String key, @Nullable Boolean value) {
     return setSingleValueParam(key, value);
   }
 
   @SuppressWarnings("unchecked")
-  private SELF setSingleValueParam(String key, @Nullable Object value) {
+  private <T extends SELF> T setSingleValueParam(String key, @Nullable Object value) {
     checkArgument(!isNullOrEmpty(key), "a WS parameter key cannot be null");
     if (value == null) {
-      return (SELF) this;
+      return (T) this;
     }
     parameters.setValue(key, value.toString());
 
-    return (SELF) this;
+    return (T) this;
   }
 
   @SuppressWarnings("unchecked")
-  public SELF setParam(String key, @Nullable Collection<? extends Object> values) {
+  public <T extends SELF> T setParam(String key, @Nullable Collection<? extends Object> values) {
     checkArgument(!isNullOrEmpty(key), "a WS parameter key cannot be null");
     if (values == null || values.isEmpty()) {
-      return (SELF) this;
+      return (T) this;
     }
 
     parameters.setValues(key, values.stream()
@@ -139,7 +139,7 @@ abstract class BaseRequest<SELF extends BaseRequest> implements WsRequest {
       .map(Object::toString)
       .collect(Collectors.toList()));
 
-    return (SELF) this;
+    return (T) this;
   }
 
   @Override
@@ -165,10 +165,10 @@ abstract class BaseRequest<SELF extends BaseRequest> implements WsRequest {
   }
 
   @SuppressWarnings("unchecked")
-  public SELF setHeader(String name, @Nullable String value) {
+  public <T extends SELF> T setHeader(String name, @Nullable String value) {
     requireNonNull(name, "Header name can't be null");
     headers.setValue(name, value);
-    return (SELF) this;
+    return (T) this;
   }
 
   private static class DefaultParameters implements Parameters {
index 7ccde46bb66d4d7280fbc4ba75ca2de4c20acfd8..92739e636f29dbf4ba97238381121d238dc1a3ee 100644 (file)
@@ -36,6 +36,7 @@ import okhttp3.OkHttpClient;
 import okhttp3.Request;
 import okhttp3.RequestBody;
 import okhttp3.Response;
+import org.sonarqube.ws.client.RequestWithPayload.Part;
 
 import static java.lang.String.format;
 import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
@@ -111,8 +112,8 @@ public class HttpConnector implements WsConnector {
     if (httpRequest instanceof GetRequest) {
       return get((GetRequest) httpRequest);
     }
-    if (httpRequest instanceof PostRequest) {
-      return post((PostRequest) httpRequest);
+    if (httpRequest instanceof RequestWithPayload) {
+      return executeRequest((RequestWithPayload) httpRequest);
     }
     throw new IllegalArgumentException(format("Unsupported implementation: %s", httpRequest.getClass()));
   }
@@ -125,28 +126,28 @@ public class HttpConnector implements WsConnector {
     return new OkHttpResponse(doCall(prepareOkHttpClient(okHttpClient, getRequest), okRequestBuilder.build()));
   }
 
-  private WsResponse post(PostRequest postRequest) {
-    HttpUrl.Builder urlBuilder = prepareUrlBuilder(postRequest);
+  private WsResponse executeRequest(RequestWithPayload<?> request) {
+    HttpUrl.Builder urlBuilder = prepareUrlBuilder(request);
 
     RequestBody body;
-    Map<String, PostRequest.Part> parts = postRequest.getParts();
-    if (postRequest.hasBody()) {
-      body = RequestBody.create(JSON, postRequest.getBody());
+    Map<String, Part> parts = request.getParts();
+    if (request.hasBody()) {
+      body = RequestBody.create(JSON, request.getBody());
     } else if (parts.isEmpty()) {
       // parameters are defined in the body (application/x-www-form-urlencoded)
       FormBody.Builder formBody = new FormBody.Builder();
-      postRequest.getParameters().getKeys()
-        .forEach(key -> postRequest.getParameters().getValues(key)
+      request.getParameters().getKeys()
+        .forEach(key -> request.getParameters().getValues(key)
           .forEach(value -> formBody.add(key, value)));
       body = formBody.build();
 
     } else {
       // parameters are defined in the URL (as GET)
-      completeUrlQueryParameters(postRequest, urlBuilder);
+      completeUrlQueryParameters(request, urlBuilder);
 
       MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
       parts.entrySet().forEach(param -> {
-        PostRequest.Part part = param.getValue();
+        Part part = param.getValue();
         bodyBuilder.addFormDataPart(
           param.getKey(),
           part.getFile().getName(),
@@ -154,9 +155,10 @@ public class HttpConnector implements WsConnector {
       });
       body = bodyBuilder.build();
     }
-    Request.Builder okRequestBuilder = prepareOkRequestBuilder(postRequest, urlBuilder).post(body);
-    Response response = doCall(prepareOkHttpClient(noRedirectOkHttpClient, postRequest), okRequestBuilder.build());
-    response = checkRedirect(response, postRequest);
+    Request.Builder okRequestBuilder = prepareOkRequestBuilder(request, urlBuilder);
+    okRequestBuilder = request.addVerbToBuilder(body).apply(okRequestBuilder);
+    Response response = doCall(prepareOkHttpClient(noRedirectOkHttpClient, request), okRequestBuilder.build());
+    response = checkRedirect(response, request);
     return new OkHttpResponse(response);
   }
 
@@ -209,7 +211,7 @@ public class HttpConnector implements WsConnector {
     }
   }
 
-  private Response checkRedirect(Response response, PostRequest postRequest) {
+  private Response checkRedirect(Response response, RequestWithPayload<?> postRequest) {
     switch (response.code()) {
       case HTTP_MOVED_PERM:
       case HTTP_MOVED_TEMP:
@@ -226,7 +228,7 @@ public class HttpConnector implements WsConnector {
     }
   }
 
-  private Response followPostRedirect(Response response, PostRequest postRequest) {
+  private Response followPostRedirect(Response response, RequestWithPayload<?> postRequest) {
     String location = response.header("Location");
     if (location == null) {
       throw new IllegalStateException(format("Missing HTTP header 'Location' in redirect of %s", response.request().url()));
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/PatchRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/PatchRequest.java
new file mode 100644 (file)
index 0000000..15d818c
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonarqube.ws.client;
+
+import java.util.function.Function;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+
+/**
+ * @since 10.0
+ */
+public class PatchRequest extends RequestWithPayload<PatchRequest> {
+
+  public PatchRequest(String path) {
+    super(path);
+  }
+
+  @Override
+  Function<Request.Builder, Request.Builder> addVerbToBuilder(RequestBody body) {
+    return builder -> builder.patch(body);
+  }
+
+  @Override
+  public Method getMethod() {
+    return Method.PATCH;
+  }
+
+}
index f3e8d7282aec09a17f9a3c621b73aa403c11b8b8..0a993e25e1b4a1419b7b936ec58d93295772e2fd 100644 (file)
  */
 package org.sonarqube.ws.client;
 
-import java.io.File;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.function.Function;
+import okhttp3.Request;
+import okhttp3.RequestBody;
 
 /**
  * @since 5.3
  */
-public class PostRequest extends BaseRequest<PostRequest> {
-
-  private String body;
-  private final Map<String, Part> parts = new LinkedHashMap<>();
+public class PostRequest extends RequestWithPayload<PostRequest> {
 
   public PostRequest(String path) {
     super(path);
   }
 
   @Override
-  public Method getMethod() {
-    return Method.POST;
-  }
-
-  public PostRequest setBody(String body) {
-    this.body = body;
-    return this;
+  Function<Request.Builder, Request.Builder> addVerbToBuilder(RequestBody body) {
+    return builder -> builder.post(body);
   }
 
-  public String getBody() {
-    return body;
-  }
-
-  public boolean hasBody() {
-    return this.body != null;
-  }
-
-  public PostRequest setPart(String name, Part part) {
-    this.parts.put(name, part);
-    return this;
-  }
-
-  public Map<String, Part> getParts() {
-    return parts;
-  }
-
-  public static class Part {
-    private final String mediaType;
-    private final File file;
-
-    public Part(String mediaType, File file) {
-      this.mediaType = mediaType;
-      this.file = file;
-    }
-
-    public String getMediaType() {
-      return mediaType;
-    }
-
-    public File getFile() {
-      return file;
-    }
+  @Override
+  public Method getMethod() {
+    return Method.POST;
   }
 
 }
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/RequestWithPayload.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/RequestWithPayload.java
new file mode 100644 (file)
index 0000000..af767a5
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonarqube.ws.client;
+
+import java.io.File;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Function;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+
+/**
+ * @since 5.3
+ */
+public abstract class RequestWithPayload<T extends RequestWithPayload<T>> extends BaseRequest<RequestWithPayload<T>> {
+
+  private String body;
+  private final Map<String, Part> parts = new LinkedHashMap<>();
+
+  protected RequestWithPayload(String path) {
+    super(path);
+  }
+
+  public T setBody(String body) {
+    this.body = body;
+    return (T) this;
+  }
+
+  public String getBody() {
+    return body;
+  }
+
+  public boolean hasBody() {
+    return this.body != null;
+  }
+
+  public T setPart(String name, Part part) {
+    this.parts.put(name, part);
+    return (T) this;
+  }
+
+  abstract Function<Request.Builder, Request.Builder> addVerbToBuilder(RequestBody body);
+
+  public Map<String, Part> getParts() {
+    return parts;
+  }
+
+  public static class Part {
+    private final String mediaType;
+    private final File file;
+
+    public Part(String mediaType, File file) {
+      this.mediaType = mediaType;
+      this.file = file;
+    }
+
+    public String getMediaType() {
+      return mediaType;
+    }
+
+    public File getFile() {
+      return file;
+    }
+  }
+
+}
index ddc4c12296123ef6f1c9b564d358d29c352032b7..c27274af5b25aa7f477945237bd03aee75f34136 100644 (file)
@@ -51,6 +51,6 @@ public interface WsRequest {
   Headers getHeaders();
 
   enum Method {
-    GET, POST
+    GET, POST, PATCH
   }
 }
index c4a60d6236c014ec267457d07dcff74b45098746..577fe35bbc66145d9d60650a461228cf9fddcce2 100644 (file)
@@ -33,6 +33,7 @@ public class SearchRequest {
   private String p;
   private String ps;
   private String q;
+  private Boolean deactivated;
 
   /**
    * Example value: "42"
@@ -68,4 +69,13 @@ public class SearchRequest {
   public String getQ() {
     return q;
   }
+
+  public Boolean getDeactivated() {
+    return deactivated;
+  }
+
+  public SearchRequest setDeactivated(Boolean deactivated) {
+    this.deactivated = deactivated;
+    return this;
+  }
 }
index da7a112b7b8d72b1f4051743a019b692ee582d40..078546cde0eb8ee70232a1f532f873b90c9d4a62 100644 (file)
@@ -148,7 +148,8 @@ public class UsersService extends BaseService {
       new GetRequest(path("search"))
         .setParam("p", request.getP())
         .setParam("ps", request.getPs())
-        .setParam("q", request.getQ()),
+        .setParam("q", request.getQ())
+        .setParam("deactivated", request.getDeactivated()),
       SearchWsResponse.parser());
   }
 
index 0ee778dde06de217e3e4aa0f9d3e5a5b1f3be382..6898c537894cb7a7d216b7dd316c7ed3afc8fc60 100644 (file)
@@ -147,7 +147,7 @@ public class ServiceTester<T extends BaseService> extends ExternalResource {
     return new RequestAssert<>(getRequest);
   }
 
-  public RequestAssert<PostRequest> assertThat(PostRequest postRequest) {
+  public RequestAssert<?> assertThat(PostRequest postRequest) {
     return new RequestAssert<>(postRequest);
   }
 
@@ -163,7 +163,7 @@ public class ServiceTester<T extends BaseService> extends ExternalResource {
 
   public PostRequest getPostRequest() {
     assertSinglePostCall();
-    return postCalls.iterator().next().getRequest();
+    return (PostRequest) postCalls.iterator().next().getRequest();
   }
 
   @CheckForNull
@@ -198,7 +198,7 @@ public class ServiceTester<T extends BaseService> extends ExternalResource {
   }
 
   @Immutable
-  public static final class PostCall extends CallWithParser<PostRequest> {
+  public static final class PostCall extends CallWithParser<RequestWithPayload<PostRequest>> {
 
     public PostCall(PostRequest postRequest, @Nullable Parser<?> parser) {
       super(postRequest, parser);