]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14395 Validate permissions for BitBucket PR decoration settings
authorMichal Duda <michal.duda@sonarsource.com>
Tue, 2 Feb 2021 13:50:33 +0000 (14:50 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 8 Feb 2021 20:07:45 +0000 (20:07 +0000)
server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java
server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/Token.java

index 1f3cc008f6a8872bdd1bf7802cd314e37f9f94f4..9f04b336b9e0b32cca5742b12afbcf84f5651c0a 100644 (file)
@@ -37,7 +37,6 @@ import okhttp3.Request;
 import okhttp3.RequestBody;
 import okhttp3.Response;
 import okhttp3.ResponseBody;
-import org.sonar.api.internal.apachecommons.io.IOUtils;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
@@ -73,47 +72,58 @@ public class BitbucketCloudRestClient {
    * Validate parameters provided.
    */
   public void validate(String clientId, String clientSecret, String workspace) {
-    String accessToken = validateAccessToken(clientId, clientSecret);
-    doGet(accessToken, buildUrl("/workspaces/" + workspace + "/permissions"), r -> null, true);
-  }
+    Token token = validateAccessToken(clientId, clientSecret);
+
+    if (token.getScopes() == null || !token.getScopes().contains("pullrequest")) {
+      String msg = "The OAuth consumer in the Bitbucket workspace is not configured with the permission to read pull requests";
+      LOG.info("Validation failed. {}}: {}", msg, token.getScopes());
+      throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS + ": " + msg);
+    }
 
-  private String validateAccessToken(String clientId, String clientSecret) {
-    Response response = null;
     try {
-      Request request = createAccessTokenRequest(clientId, clientSecret);
-      response = client.newCall(request).execute();
+      doGet(token.getAccessToken(), buildUrl("/repositories/" + workspace), r -> null);
+    } catch (NotFoundException | IllegalStateException e) {
+      throw new IllegalArgumentException(e.getMessage());
+    }
+  }
+
+  private Token validateAccessToken(String clientId, String clientSecret) {
+    Request request = createAccessTokenRequest(clientId, clientSecret);
+    try (Response response = client.newCall(request).execute()) {
       if (response.isSuccessful()) {
-        return buildGson().fromJson(response.body().charStream(), Token.class).getAccessToken();
+        return buildGson().fromJson(response.body().charStream(), Token.class);
       }
 
       ErrorDetails errorMsg = getTokenError(response.body());
-      if (errorMsg.parsedErrorMsg != null) {
-        switch (errorMsg.parsedErrorMsg) {
+      if (errorMsg.body != null) {
+        switch (errorMsg.body) {
           case "invalid_grant":
             throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS +
               ": Configure the OAuth consumer in the Bitbucket workspace to be a private consumer");
           case "unauthorized_client":
             throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS + ": Check your credentials");
           default:
-            LOG.info("Validation failed: " + errorMsg.parsedErrorMsg);
+            if (errorMsg.parsedErrorMsg != null) {
+              LOG.info("Validation failed: " + errorMsg.parsedErrorMsg);
+              throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS + ": " + errorMsg.parsedErrorMsg);
+            } else {
+              LOG.info("Validation failed: " + errorMsg.body);
+              throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS);
+            }
         }
       } else {
-        LOG.info("Validation failed: " + errorMsg.body);
+        LOG.info("Validation failed");
       }
       throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS);
 
     } catch (IOException e) {
       throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS, e);
-    } finally {
-      if (response != null && response.body() != null) {
-        IOUtils.closeQuietly(response);
-      }
     }
   }
 
   public String createAccessToken(String clientId, String clientSecret) {
     Request request = createAccessTokenRequest(clientId, clientSecret);
-    return doCall(request, r -> buildGson().fromJson(r.body().charStream(), Token.class), false).getAccessToken();
+    return doCall(request, r -> buildGson().fromJson(r.body().charStream(), Token.class)).getAccessToken();
   }
 
   private Request createAccessTokenRequest(String clientId, String clientSecret) {
@@ -133,35 +143,31 @@ public class BitbucketCloudRestClient {
     return HttpUrl.parse(removeEnd(bitbucketCloudEndpoint, "/") + "/" + VERSION + relativeUrl);
   }
 
-  protected <G> G doGet(String accessToken, HttpUrl url, Function<Response, G> handler, boolean throwErrorDetails) {
-    Request request = prepareRequestWithAccessToken(accessToken, GET, url, null);
-    return doCall(request, handler, throwErrorDetails);
-  }
-
   protected <G> G doGet(String accessToken, HttpUrl url, Function<Response, G> handler) {
-    return doGet(accessToken, url, handler, false);
+    Request request = prepareRequestWithAccessToken(accessToken, GET, url, null);
+    return doCall(request, handler);
   }
 
   protected void doPost(String accessToken, HttpUrl url, RequestBody body) {
     Request request = prepareRequestWithAccessToken(accessToken, "POST", url, body);
-    doCall(request, r -> null, false);
+    doCall(request, r -> null);
   }
 
   protected void doPut(String accessToken, HttpUrl url, String json) {
     RequestBody body = RequestBody.create(json, JSON_MEDIA_TYPE);
     Request request = prepareRequestWithAccessToken(accessToken, "PUT", url, body);
-    doCall(request, r -> null, false);
+    doCall(request, r -> null);
   }
 
   protected void doDelete(String accessToken, HttpUrl url) {
     Request request = prepareRequestWithAccessToken(accessToken, "DELETE", url, null);
-    doCall(request, r -> null, false);
+    doCall(request, r -> null);
   }
 
-  private <G> G doCall(Request request, Function<Response, G> handler, boolean throwErrorDetails) {
+  private <G> G doCall(Request request, Function<Response, G> handler) {
     try (Response response = client.newCall(request).execute()) {
       if (!response.isSuccessful()) {
-        handleError(response, throwErrorDetails);
+        handleError(response);
       }
       return handler.apply(response);
     } catch (IOException e) {
@@ -169,58 +175,17 @@ public class BitbucketCloudRestClient {
     }
   }
 
-  private static Request prepareRequestWithAccessToken(String accessToken, String method, HttpUrl url, @Nullable RequestBody body) {
-    return new Request.Builder()
-      .method(method, body)
-      .url(url)
-      .header("Authorization", "Bearer " + accessToken)
-      .build();
-  }
-
-  public static Gson buildGson() {
-    return new GsonBuilder().create();
-  }
-
-  private static ErrorDetails getTokenError(@Nullable ResponseBody body) throws IOException {
-    return getErrorDetails(body, s -> {
-      TokenError gsonError = buildGson().fromJson(s, TokenError.class);
-      if (gsonError != null && gsonError.error != null) {
-        return gsonError.error;
-      }
-      return null;
-    });
-  }
-
-  private static ErrorDetails getErrorDetails(@Nullable ResponseBody body, UnaryOperator<String> parser) throws IOException {
-    if (body == null) {
-      return new ErrorDetails("", null);
-    }
-    String bodyStr = body.string();
-    if (body.contentType() != null && Objects.equals(JSON_MEDIA_TYPE.type(), body.contentType().type())) {
-      try {
-        return new ErrorDetails(bodyStr, parser.apply(bodyStr));
-      } catch (JsonParseException e) {
-        //ignore
-      }
-    }
-    return new ErrorDetails(bodyStr, null);
-  }
-
-  private static void handleError(Response response, boolean throwErrorDetails) throws IOException {
+  private static void handleError(Response response) throws IOException {
     int responseCode = response.code();
     ErrorDetails error = getError(response.body());
     if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
       String errorMsg = error.parsedErrorMsg != null ? error.parsedErrorMsg : "";
-      if (throwErrorDetails) {
-        throw new IllegalArgumentException(errorMsg);
-      } else {
-        throw new NotFoundException(errorMsg);
-      }
+      throw new NotFoundException(errorMsg);
     }
     LOG.info(UNABLE_TO_CONTACT_BBC_SERVERS + ": {} {}", responseCode, error.parsedErrorMsg != null ? error.parsedErrorMsg : error.body);
 
-    if (throwErrorDetails && error.parsedErrorMsg != null) {
-      throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS + ": " + error.parsedErrorMsg);
+    if (error.parsedErrorMsg != null) {
+      throw new IllegalStateException(UNABLE_TO_CONTACT_BBC_SERVERS + ": " + error.parsedErrorMsg);
     } else {
       throw new IllegalStateException(UNABLE_TO_CONTACT_BBC_SERVERS);
     }
@@ -236,14 +201,61 @@ public class BitbucketCloudRestClient {
     });
   }
 
+  private static ErrorDetails getTokenError(@Nullable ResponseBody body) throws IOException {
+    if (body == null) {
+      return new ErrorDetails(null, null);
+    }
+    String bodyStr = body.string();
+    if (body.contentType() != null && Objects.equals(JSON_MEDIA_TYPE.type(), body.contentType().type())) {
+      try {
+        TokenError gsonError = buildGson().fromJson(bodyStr, TokenError.class);
+        if (gsonError != null && gsonError.error != null) {
+          return new ErrorDetails(gsonError.error, gsonError.errorDescription);
+        }
+      } catch (JsonParseException e) {
+        // ignore
+      }
+    }
+
+    return new ErrorDetails(bodyStr, null);
+  }
+
   private static class ErrorDetails {
-    private String body;
     @Nullable
-    private String parsedErrorMsg;
+    private final String body;
+    @Nullable
+    private final String parsedErrorMsg;
 
-    public ErrorDetails(String body, @Nullable String parsedErrorMsg) {
+    public ErrorDetails(@Nullable String body, @Nullable String parsedErrorMsg) {
       this.body = body;
       this.parsedErrorMsg = parsedErrorMsg;
     }
   }
+
+  private static ErrorDetails getErrorDetails(@Nullable ResponseBody body, UnaryOperator<String> parser) throws IOException {
+    if (body == null) {
+      return new ErrorDetails("", null);
+    }
+    String bodyStr = body.string();
+    if (body.contentType() != null && Objects.equals(JSON_MEDIA_TYPE.type(), body.contentType().type())) {
+      try {
+        return new ErrorDetails(bodyStr, parser.apply(bodyStr));
+      } catch (JsonParseException e) {
+        // ignore
+      }
+    }
+    return new ErrorDetails(bodyStr, null);
+  }
+
+  private static Request prepareRequestWithAccessToken(String accessToken, String method, HttpUrl url, @Nullable RequestBody body) {
+    return new Request.Builder()
+      .method(method, body)
+      .url(url)
+      .header("Authorization", "Bearer " + accessToken)
+      .build();
+  }
+
+  public static Gson buildGson() {
+    return new GsonBuilder().create();
+  }
 }
index 2933ecf94860bf219da176984e93a2704130e05b..fc5fec3c499b632cacd2315f7fcc5a2c90189934 100644 (file)
@@ -22,7 +22,7 @@ package org.sonar.alm.client.bitbucket.bitbucketcloud;
 import com.google.gson.annotations.SerializedName;
 
 public class Token {
-  @SerializedName("scope")
+  @SerializedName("scopes")
   private String scopes;
   @SerializedName("access_token")
   private String accessToken;