diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 7cc13ff3c..8c3d2a7d5 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -2,123 +2,25 @@ ## Reporting a Vulnerability -For reporting security-related vulnerabilities or exploits that [haven't been reported yet](https://github.com/cryptomator/cryptomator/labels/type%3Asecurity-issue), contact us at: security@cryptomator.org +We take security seriously at Cryptomator. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. -
-PGP Key +To report a security vulnerability, please use the [GitHub Security Advisory feature](https://github.com/cryptomator/cryptomator/security/advisories). This feature allows you to privately discuss, fix, and publish information about security vulnerabilities. -``` ------BEGIN PGP PUBLIC KEY BLOCK----- -Comment: GPGTools - https://gpgtools.org +If you prefer to report the vulnerability via email, please send an email to security@cryptomator.org. -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 -=W1Ri ------END PGP PUBLIC KEY BLOCK----- -``` -
+PGP key fingerprint: `3647 9903 B23A E0A5 9359  9A3E 23B5 DBEF 94D4 D81D` ([public key](https://gist.github.com/cryptobot/864300b6b44ae2d2a15abedfe14bd040)) + +## Expectations + +When reporting a vulnerability, please provide us with a detailed report that includes: + +- A description of the vulnerability +- Steps to reproduce the vulnerability +- Possible impact of the vulnerability +- Any additional information that may be helpful + +We ask that you do not publicly disclose the vulnerability until we have had a chance to address it. + +## Thank You + +We appreciate your help in keeping Cryptomator secure. Thank you for your contributions to the security of our project. diff --git a/src/main/java/org/cryptomator/common/ErrorCode.java b/src/main/java/org/cryptomator/common/ErrorCode.java index 7def1287b..7363e2278 100644 --- a/src/main/java/org/cryptomator/common/ErrorCode.java +++ b/src/main/java/org/cryptomator/common/ErrorCode.java @@ -32,18 +32,15 @@ public class ErrorCode { this.rootCauseSpecificFrames = rootCauseSpecificFrames; } - // visible for testing - String methodCode() { + public String methodCode() { return format(traceCode(rootCause, LATEST_FRAME)); } - // visible for testing - String rootCauseCode() { + public String rootCauseCode() { return format(traceCode(rootCause, rootCauseSpecificFrames)); } - // visible for testing - String throwableCode() { + public String throwableCode() { return format(traceCode(throwable, ALL_FRAMES)); } diff --git a/src/main/java/org/cryptomator/ui/error/ErrorController.java b/src/main/java/org/cryptomator/ui/error/ErrorController.java index 422a44157..620a1f730 100644 --- a/src/main/java/org/cryptomator/ui/error/ErrorController.java +++ b/src/main/java/org/cryptomator/ui/error/ErrorController.java @@ -1,5 +1,7 @@ package org.cryptomator.ui.error; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; import org.cryptomator.common.Environment; import org.cryptomator.common.ErrorCode; import org.cryptomator.common.Nullable; @@ -9,20 +11,34 @@ import javax.inject.Inject; import javax.inject.Named; import javafx.application.Application; import javafx.application.Platform; +import javafx.beans.binding.BooleanExpression; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.FXML; import javafx.scene.Scene; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.stage.Stage; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; +import java.util.Comparator; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; public class ErrorController implements FxController { + private static final String ERROR_CODES_URL = "https://gist.githubusercontent.com/cryptobot/accba9fb9555e7192271b85606f97230/raw/errorcodes.json"; private static final String SEARCH_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/categories/errors?discussions_q=category:Errors+%s"; private static final String REPORT_URL_FORMAT = "https://github.com/cryptomator/cryptomator/discussions/new?category=Errors&title=Error+%s&body=%s"; private static final String SEARCH_ERRORCODE_DELIM = " OR "; @@ -46,16 +62,28 @@ public class ErrorController implements FxController { private final Stage window; private final Environment environment; - private BooleanProperty copiedDetails = new SimpleBooleanProperty(); + private final BooleanProperty copiedDetails = new SimpleBooleanProperty(); + private final ObjectProperty matchingErrorDiscussion = new SimpleObjectProperty<>(); + private final BooleanExpression errorSolutionFound = matchingErrorDiscussion.isNotNull(); + private final BooleanProperty isLoadingHttpResponse = new SimpleBooleanProperty(); @Inject - ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window, Environment environment) { + ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window, Environment environment, ExecutorService executorService) { this.application = application; this.stackTrace = stackTrace; this.errorCode = errorCode; this.previousScene = previousScene; this.window = window; this.environment = environment; + + isLoadingHttpResponse.set(true); + HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build(); + HttpRequest httpRequest = HttpRequest.newBuilder()// + .uri(URI.create(ERROR_CODES_URL))// + .build(); + httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofInputStream())// + .thenAcceptAsync(this::loadHttpResponse, executorService)// + .whenCompleteAsync((r, e) -> isLoadingHttpResponse.set(false), Platform::runLater); } @FXML @@ -70,6 +98,16 @@ public class ErrorController implements FxController { window.close(); } + @FXML + public void showSolution() { + if (matchingErrorDiscussion.isNotNull().get()) { + var discussion = matchingErrorDiscussion.get(); + if (discussion != null) { + application.getHostServices().showDocument(discussion.url); + } + } + } + @FXML public void searchError() { var searchTerm = URLEncoder.encode(getErrorCode().replace(ErrorCode.DELIM, SEARCH_ERRORCODE_DELIM), StandardCharsets.UTF_8); @@ -95,13 +133,119 @@ public class ErrorController implements FxController { Clipboard.getSystemClipboard().setContent(clipboardContent); copiedDetails.set(true); - CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> { - copiedDetails.set(false); - }); + CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS, Platform::runLater).execute(() -> copiedDetails.set(false)); + } + + private void loadHttpResponse(HttpResponse response) { + if (response.statusCode() == 200) { + Map errorDiscussionMap = new Gson().fromJson(// + new InputStreamReader(response.body(), StandardCharsets.UTF_8),// + new TypeToken>() { + }.getType()); + + if (errorDiscussionMap.values().stream().anyMatch(this::containsMethodCode)) { + Comparator comp = this::compareByFullErrorCode; + Optional value = errorDiscussionMap.values().stream().filter(this::containsMethodCode)// + .min(comp// + .thenComparing(this::compareByRootCauseCode)// + .thenComparing(this::compareIsAnswered)// + .thenComparing(this::compareUpvoteCount)); + + if (value.isPresent()) { + matchingErrorDiscussion.set(value.get()); + } + } + } + } + + /** + * Checks if an ErrorDiscussion object's title contains the error code's method code. + * + * @param errorDiscussion The ErrorDiscussion object to be checked. + * @return A boolean value indicating if the ErrorDiscussion object's title contains the error code's method code: + * - true if the title contains the method code, + * - false otherwise. + */ + public boolean containsMethodCode(ErrorDiscussion errorDiscussion) { + return errorDiscussion.title.contains(" " + errorCode.methodCode()); + } + + /** + * Compares two ErrorDiscussion objects based on their upvote counts and returns the result. + * + * @param ed1 The first ErrorDiscussion object. + * @param ed2 The second ErrorDiscussion object. + * @return An integer indicating which ErrorDiscussion object has a higher upvote count: + * - A positive value if ed2 has a higher upvote count than ed1, + * - A negative value if ed1 has a higher upvote count than ed2, + * - Or 0 if both upvote counts are equal. + */ + public int compareUpvoteCount(ErrorDiscussion ed1, ErrorDiscussion ed2) { + return Integer.compare(ed2.upvoteCount, ed1.upvoteCount); + } + + /** + * Compares two ErrorDiscussion objects based on their answered status and returns the result. + * + * @param ed1 The first ErrorDiscussion object. + * @param ed2 The second ErrorDiscussion object. + * @return An integer indicating the answered status of the ErrorDiscussion objects: + * - A negative value (-1) if ed1 is considered answered and ed2 is considered unanswered, + * - A positive value (1) if ed1 is considered unanswered and ed2 is considered answered, + * - Or 0 if both ErrorDiscussion objects are considered answered or unanswered. + */ + public int compareIsAnswered(ErrorDiscussion ed1, ErrorDiscussion ed2) { + if (ed1.answer != null && ed2.answer == null) { + return -1; + } else if (ed1.answer == null && ed2.answer != null) { + return 1; + } else { + return 0; + } + } + + /** + * Compares two ErrorDiscussion objects based on the presence of the full error code in their titles and returns the result. + * + * @param ed1 The first ErrorDiscussion object. + * @param ed2 The second ErrorDiscussion object. + * @return An integer indicating the comparison result based on the presence of the full error code in the titles: + * - A negative value (-1) if ed1 contains the full error code in the title and ed2 does not have a match, + * - A positive value (1) if ed1 does not have a match and ed2 contains the full error code in the title, + * - Or 0 if both ErrorDiscussion objects either contain the full error code or do not have a match in the titles. + */ + public int compareByFullErrorCode(ErrorDiscussion ed1, ErrorDiscussion ed2) { + if (ed1.title.contains(getErrorCode()) && !ed2.title.contains(getErrorCode())) { + return -1; + } else if (!ed1.title.contains(getErrorCode()) && ed2.title.contains(getErrorCode())) { + return 1; + } else { + return 0; + } + } + + /** + * Compares two ErrorDiscussion objects based on the presence of the root cause code in their titles and returns the result. + * + * @param ed1 The first ErrorDiscussion object. + * @param ed2 The second ErrorDiscussion object. + * @return An integer indicating the comparison result based on the presence of the root cause code in the titles: + * - A negative value (-1) if ed1 contains the root cause code in the title and ed2 does not have a match, + * - A positive value (1) if ed1 does not have a match and ed2 contains the root cause code in the title, + * - Or 0 if both ErrorDiscussion objects either contain the root cause code or do not have a match in the titles. + */ + public int compareByRootCauseCode(ErrorDiscussion ed1, ErrorDiscussion ed2) { + String value = " " + errorCode.methodCode() + ErrorCode.DELIM + errorCode.rootCauseCode(); + if (ed1.title.contains(value) && !ed2.title.contains(value)) { + return -1; + } else if (!ed1.title.contains(value) && ed2.title.contains(value)) { + return 1; + } else { + return 0; + } } /* Getter/Setter */ - public boolean isPreviousScenePresent() { return previousScene != null; } @@ -125,4 +269,21 @@ public class ErrorController implements FxController { public boolean getCopiedDetails() { return copiedDetails.get(); } -} + + public BooleanExpression errorSolutionFoundProperty() { + return errorSolutionFound; + } + + public boolean getErrorSolutionFound() { + return errorSolutionFound.get(); + } + + public BooleanProperty isLoadingHttpResponseProperty() { + return isLoadingHttpResponse; + } + + public boolean getIsLoadingHttpResponse() { + return isLoadingHttpResponse.get(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/error/ErrorDiscussion.java b/src/main/java/org/cryptomator/ui/error/ErrorDiscussion.java new file mode 100644 index 000000000..bc1663f6f --- /dev/null +++ b/src/main/java/org/cryptomator/ui/error/ErrorDiscussion.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.error; + +public class ErrorDiscussion { + + int upvoteCount; + String title; + String url; + Answer answer; + + static class Answer { + + } +} diff --git a/src/main/resources/fxml/error.fxml b/src/main/resources/fxml/error.fxml index ca0966a0a..cc11db58a 100644 --- a/src/main/resources/fxml/error.fxml +++ b/src/main/resources/fxml/error.fxml @@ -1,6 +1,7 @@ + @@ -31,20 +32,28 @@ - -