Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add onReceivedHttpAuthRequest #72

Merged
merged 9 commits into from Sep 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cirrus.yml
Expand Up @@ -30,7 +30,7 @@ flutter_drive_android_task:
env:
EMULATOR_API_LEVEL: 29
ANDROID_ABI: "default;x86"
fix_kvm_script: sudo chown cirrus:cirrus /dev/kvm
fix_kvm_script: sudo chown $(whoami):$(whoami) /dev/kvm
update_emulator_script: sdkmanager --verbose --channel=0 "emulator"
update_images_script: sdkmanager "system-images;android-$EMULATOR_API_LEVEL;$ANDROID_ABI"
create_device_script: echo no | avdmanager create avd --force -n test -k "system-images;android-$EMULATOR_API_LEVEL;$ANDROID_ABI"
Expand Down
Expand Up @@ -13,6 +13,41 @@ class NativeWebViewClient(private val channel: MethodChannel, private val option
ContentBlocker.fromMap(it)
})

override fun onReceivedHttpAuthRequest(view: WebView, handler: HttpAuthHandler, host: String, realm: String) {
channel.invokeMethod(
"onReceivedHttpAuthRequest",
mapOf("host" to host, "realm" to realm),
object : MethodChannel.Result {
override fun notImplemented() {
Log.i("NativeWebViewClient", "onReceivedHttpAuthRequest is notImplemented")
handler.cancel()
}

override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {
Log.e("NativeWebViewClient", "$errorCode $errorMessage $errorDetails")
handler.cancel()
}

override fun success(response: Any?) {
val responseMap = response as? Map<String, Any>
if (responseMap != null) {
when (responseMap["action"] as? Int ?: 1) {
0 -> {
val username = responseMap["username"] as String
val password = responseMap["password"] as String
handler.proceed(username, password)
}
1 -> handler.cancel()
else -> handler.cancel()
}
return
}

handler.cancel()
}
})
}

override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest): WebResourceResponse? {
if (contentBlockerHandler.ruleList.isNotEmpty() && view != null) {
try {
Expand Down Expand Up @@ -62,11 +97,11 @@ class NativeWebViewClient(private val channel: MethodChannel, private val option
),
object : MethodChannel.Result {
override fun notImplemented() {
Log.i("NativeWebChromeClient", "shouldOverrideUrlLoading is notImplemented")
Log.i("NativeWebViewClient", "shouldOverrideUrlLoading is notImplemented")
}

override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {
Log.e("NativeWebChromeClient", "$errorCode $errorMessage $errorDetails")
Log.e("NativeWebViewClient", "$errorCode $errorMessage $errorDetails")
}

override fun success(response: Any?) {
Expand Down
50 changes: 50 additions & 0 deletions example/test_driver/tests/webview_test.dart
Expand Up @@ -295,6 +295,56 @@ void main() {
});
});

group("onReceivedHttpAuthRequest", () {
testWebView('wait for page finished', (tester, context) async {
List<HttpAuthChallenge> challengeValues = [];

await tester.pumpWidget(
WebView(
initialUrl: 'https://native-webview-basic-auth.herokuapp.com/',
onReceivedHttpAuthRequest: (controller, challenge) async {
challengeValues.add(challenge);

return ReceivedHttpAuthResponse.useCredential(
"username",
"password",
);
},
onWebViewCreated: context.onWebViewCreated,
onPageFinished: context.onPageFinished,
),
);

final controller = await context.webviewController.future;
context.pageFinished.stream.listen(onData([
(event) async {
final text = await controller.evaluateJavascript(
"document.body.textContent",
);
expect(challengeValues.length, greaterThanOrEqualTo(1));

expect(
challengeValues
.where((element) =>
element.realm?.contains("Authorization Required") ??
false)
.length,
greaterThanOrEqualTo(1));

expect(
challengeValues
.where((element) =>
element.host == "native-webview-basic-auth.herokuapp.com")
.length,
greaterThanOrEqualTo(1));

expect(text.toString().contains("Hello world"), true);
context.complete();
},
]));
});
});

group("gestureNavigationEnabled", () {
testWebView('is true', (tester, context) async {
List<int> progressValues = [];
Expand Down
37 changes: 37 additions & 0 deletions ios/Classes/NativeWebView.swift
Expand Up @@ -292,6 +292,43 @@ extension NativeWebView: WKUIDelegate {
onWebResourceError(error as NSError)
}

public func webView(
_ webView: WKWebView,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
let host = challenge.protectionSpace.host
let realm = challenge.protectionSpace.realm
let arguments = ["host": host, "realm": realm]
channel?.invokeMethod("onReceivedHttpAuthRequest", arguments: arguments, result: { (result) -> Void in
if result is FlutterError, let result = result as? FlutterError {
NSLog("\n\(result.message ?? "message is nil")")
return
}


if let result = result, let response = result as? [String: Any] {
let action = response["action"] as? Int
switch action {
case 0: // useCredential
let username = response["username"] as! String
let password = response["password"] as! String
let credential = URLCredential(
user: username,
password: password,
persistence: URLCredential.Persistence.forSession
)
completionHandler(.useCredential, credential)
default: // Cancel
// Use .performDefaultHandling to make it behave the same as Android
completionHandler(.performDefaultHandling, nil)
}
return
}

})
}

private func createPromptDialog(
message: String,
defaultText: String?,
Expand Down
57 changes: 57 additions & 0 deletions lib/src/webview.dart
Expand Up @@ -79,6 +79,57 @@ extension ShouldOverrideUrlLoadingActionExtension
}
}

class HttpAuthChallenge {
final String host;
final String realm;

HttpAuthChallenge({this.host, this.realm});
}

enum ReceivedHttpAuthResponseAction {
useCredential,
cancel,
}

class ReceivedHttpAuthResponse {
final ReceivedHttpAuthResponseAction action;
final String username;
final String password;

factory ReceivedHttpAuthResponse.useCredential(
String username,
String password,
) {
return ReceivedHttpAuthResponse._(
ReceivedHttpAuthResponseAction.useCredential,
username,
password,
);
}

factory ReceivedHttpAuthResponse.cancel() {
return ReceivedHttpAuthResponse._(
ReceivedHttpAuthResponseAction.cancel,
null,
null,
);
}

ReceivedHttpAuthResponse._(
this.action,
this.username,
this.password,
);

Map<String, dynamic> toMap() {
return {
"action": action?.index ?? ReceivedHttpAuthResponseAction.cancel.index,
"username": username,
"password": password,
};
}
}

class WebView extends StatefulWidget {
final String initialUrl;
final String initialFile;
Expand All @@ -100,6 +151,11 @@ class WebView extends StatefulWidget {
ShouldOverrideUrlLoadingRequest,
) shouldOverrideUrlLoading;

final Future<ReceivedHttpAuthResponse> Function(
WebViewController,
HttpAuthChallenge,
) onReceivedHttpAuthRequest;

final List<ContentBlocker> contentBlockers;

/// Controls whether WebView debugging is enabled.
Expand Down Expand Up @@ -139,6 +195,7 @@ class WebView extends StatefulWidget {
this.onPageFinished,
this.onWebResourceError,
this.onProgressChanged,
this.onReceivedHttpAuthRequest,
this.onJsConfirm,
this.onJsAlert,
this.onJsPrompt,
Expand Down
13 changes: 13 additions & 0 deletions lib/src/webview_controller.dart
Expand Up @@ -104,6 +104,19 @@ class WebViewController {
isForMainFrame: isForMainFrame,
);
return (await _widget.shouldOverrideUrlLoading(this, request))?.toMap();
case 'onReceivedHttpAuthRequest':
if (_widget.onReceivedHttpAuthRequest == null) {
return {};
}

final host = call.arguments['host'] as String;
final realm = call.arguments['realm'] as String;
final request = HttpAuthChallenge(
host: host,
realm: realm,
);
return (await _widget.onReceivedHttpAuthRequest(this, request))
?.toMap();
}
throw MissingPluginException(
'${call.method} was invoked but has no handler',
Expand Down