Skip to content

Commit

Permalink
fix: report insecure request error (#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
CAMOBAP authored Dec 8, 2023
1 parent d9f8299 commit 725210b
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 23 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

# 3.10.0

- Fix: crash on insecure HTTP request handling
- Feat: new error code `INSECURE_HTTP_REQUEST_ERROR`
# 3.9.1

- Fix: add missing ProGuard rules for enums
Expand Down
2 changes: 1 addition & 1 deletion MAINTAINERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ To publish a new version follow the next steps:
1. Bump versions in the [`sdk/build.gradle`](./sdk/build.gradle) file:
* `android.defaultConfig.versionCode`: increment by **1** (next integer)
* `android.defaultConfig.versionName`: [Semantic Versioning](https://semver.org)
2. Update [`CHANGELOG.md`](./CHANGELOG.md) with changes since last version
2. Update [`CHANGES.md`](./CHANGES.md) with changes since last version
3. Create a [Github Release](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/managing-releases-in-a-repository#creating-a-release) with the **SAME** version from step 1 (**without** a prefix such as `v`)
* JitPack's automatic process will be triggered upon first installation of the new package version

Expand Down
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,18 +265,19 @@ You can add logic to gracefully handle the errors.
The following is a list of possible error codes:
| Name | Code | Description |
|------------------------|-------|----------------------------------------------------|
| `NETWORK_ERROR` | 7 | There is no internet connection. |
| `INVALID_DATA` | 8 | Invalid data is not accepted by endpoints. |
| `CHALLENGE_ERROR` | 9 | JS client encountered an error on challenge setup. |
| `INTERNAL_ERROR` | 10 | JS client encountered an internal error. |
| `SESSION_TIMEOUT` | 15 | The challenge expired. |
| `TOKEN_TIMEOUT` | 16 | The token expired. |
| `CHALLENGE_CLOSED` | 30 | The challenge was closed by the user. |
| `RATE_LIMITED` | 31 | Spam detected. |
| `INVALID_CUSTOM_THEME` | 32 | Invalid custom theme. |
| `ERROR` | 29 | General failure. |
| Name | Code | Description |
|-------------------------------|------|----------------------------------------------------|
| `NETWORK_ERROR` | 7 | There is no internet connection. |
| `INVALID_DATA` | 8 | Invalid data is not accepted by endpoints. |
| `CHALLENGE_ERROR` | 9 | JS client encountered an error on challenge setup. |
| `INTERNAL_ERROR` | 10 | JS client encountered an internal error. |
| `SESSION_TIMEOUT` | 15 | The challenge expired. |
| `TOKEN_TIMEOUT` | 16 | The token expired. |
| `CHALLENGE_CLOSED` | 30 | The challenge was closed by the user. |
| `RATE_LIMITED` | 31 | Spam detected. |
| `INVALID_CUSTOM_THEME` | 32 | Invalid custom theme. |
| `INSECURE_HTTP_REQUEST_ERROR` | 33 | Insecure resource requested. |
| `ERROR` | 29 | General failure. |
### Retry Failed Verification
Expand Down
4 changes: 2 additions & 2 deletions sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ android {
// See https://developer.android.com/studio/publish/versioning
// versionCode must be integer and be incremented by one for every new update
// android system uses this to prevent downgrades
versionCode 36
versionCode 37

// version number visible to the user
// should follow semantic versioning (See https://semver.org)
versionName "3.9.1"
versionName "3.10.0"

buildConfigField 'String', 'VERSION_NAME', "\"${defaultConfig.versionName}_${defaultConfig.versionCode}\""

Expand Down
5 changes: 5 additions & 0 deletions sdk/src/main/java/com/hcaptcha/sdk/HCaptchaError.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ public enum HCaptchaError implements Serializable {
*/
INVALID_CUSTOM_THEME(32, "Invalid custom theme"),

/**
* Insecure HTTP request intercepted
*/
INSECURE_HTTP_REQUEST_ERROR(33, "Insecure resource requested"),

/**
* Generic error for unknown situations - should never happen.
*/
Expand Down
16 changes: 12 additions & 4 deletions sdk/src/main/java/com/hcaptcha/sdk/HCaptchaException.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;


/**
Expand All @@ -18,20 +19,28 @@ public class HCaptchaException extends Exception {
/**
* The hCaptcha error object
*/
@NonNull
private final HCaptchaError hCaptchaError;

@Nullable
private final String message;

public HCaptchaException(HCaptchaError error) {
this(error, null);
}

/**
* @return The {@link HCaptchaError} error
*/
public HCaptchaError getHCaptchaError() {
return this.hCaptchaError;
return hCaptchaError;
}

/**
* @return The error id
*/
public int getStatusCode() {
return this.hCaptchaError.getErrorId();
return hCaptchaError.getErrorId();
}

/**
Expand All @@ -40,7 +49,6 @@ public int getStatusCode() {
@Nullable
@Override
public String getMessage() {
return this.hCaptchaError.getMessage();
return message == null ? hCaptchaError.getMessage() : message;
}

}
23 changes: 19 additions & 4 deletions sdk/src/main/java/com/hcaptcha/sdk/HCaptchaWebViewHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private void setupWebView(@NonNull final Handler handler) {
settings.setAllowFileAccess(false);
settings.setAllowContentAccess(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.setWebViewClient(new HCaptchaWebClient());
webView.setWebViewClient(new HCaptchaWebClient(handler, listener));
}
if (HCaptchaLog.sDiagnosticsLogEnabled) {
webView.setWebChromeClient(new HCaptchaWebChromeClient());
Expand Down Expand Up @@ -123,16 +123,31 @@ public boolean shouldRetry(HCaptchaException exception) {
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private class HCaptchaWebClient extends WebViewClient {

@NonNull
private final Handler handler;

@NonNull
private final HCaptchaStateListener listener;

HCaptchaWebClient(@NonNull Handler handler, @NonNull HCaptchaStateListener listener) {
this.handler = handler;
this.listener = listener;
}

private String stripUrl(String url) {
return url.split("[?#]")[0] + "...";
}

@Override
public WebResourceResponse shouldInterceptRequest (final WebView view, final WebResourceRequest request) {
final Uri requestUri = request.getUrl();
if (requestUri != null && requestUri.getScheme().equals("http")) {
webView.removeJavascriptInterface(HCaptchaJSInterface.JS_INTERFACE_TAG);
webView.removeJavascriptInterface(HCaptchaDebugInfo.JS_INTERFACE_TAG);
if (requestUri != null && requestUri.getScheme() != null && requestUri.getScheme().equals("http")) {
handler.post(() -> {
webView.removeJavascriptInterface(HCaptchaJSInterface.JS_INTERFACE_TAG);
webView.removeJavascriptInterface(HCaptchaDebugInfo.JS_INTERFACE_TAG);
listener.onFailure(new HCaptchaException(HCaptchaError.INSECURE_HTTP_REQUEST_ERROR,
"Insecure resource " + requestUri + " requested"));
});
}
return super.shouldInterceptRequest(view, request);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.hcaptcha.sdk;

import static com.hcaptcha.sdk.AssertUtil.failAsNonReachable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

import android.os.Handler;
import android.os.Looper;
import android.webkit.WebView;

import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.rules.ActivityScenarioRule;

import com.hcaptcha.sdk.test.TestActivity;

import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class HCaptchaWebViewHelperTest {
private static final long AWAIT_CALLBACK_MS = 5000;

@Rule
public ActivityScenarioRule<TestActivity> rule = new ActivityScenarioRule<>(TestActivity.class);

final HCaptchaConfig baseConfig = HCaptchaConfig.builder()
.siteKey("10000000-ffff-ffff-ffff-000000000001")
.hideDialog(true)
.tokenExpiration(1)
.build();

final HCaptchaInternalConfig internalConfig = HCaptchaInternalConfig.builder()
.htmlProvider(new HCaptchaTestHtml())
.build();

@Test
public void testInsecureHttpRequestErrorHandling() throws Exception {
Assume.assumeTrue("Skip test for release, because impossible to mock IHCaptchaVerifier", BuildConfig.DEBUG);

final Handler handler = new Handler(Looper.getMainLooper());
final CountDownLatch failureLatch = new CountDownLatch(1);
final HCaptchaConfig config = baseConfig.toBuilder().host("http://localhost").build();
final IHCaptchaVerifier verifier = mock(IHCaptchaVerifier.class);

final HCaptchaStateListener listener = new HCaptchaStateTestAdapter() {

@Override
void onSuccess(String token) {
failAsNonReachable();
}

@Override
void onFailure(HCaptchaException e) {
assertEquals(HCaptchaError.INSECURE_HTTP_REQUEST_ERROR, e.getHCaptchaError());
assertEquals("Insecure resource http://localhost/favicon.ico requested", e.getMessage());
failureLatch.countDown();
}
};

final ActivityScenario<TestActivity> scenario = rule.getScenario();
scenario.onActivity(activity -> {
WebView webView = new WebView(activity);
final HCaptchaWebViewHelper helper = new HCaptchaWebViewHelper(
handler, activity, config, internalConfig, verifier, listener, webView);
});

assertTrue(failureLatch.await(AWAIT_CALLBACK_MS, TimeUnit.MILLISECONDS));
}
}

0 comments on commit 725210b

Please sign in to comment.