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

[webview_flutter_wkwebview] Fixes crash where the native AuthenticationChallengeResponse could not be found for auth requests #8707

Merged
merged 10 commits into from
Feb 28, 2025
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 3.18.3

* Fixes crash where the native `AuthenticationChallengeResponse` could not be found for auth
requests.

## 3.18.2

* Updates generated pigeon code to ensure the internal wrapper immediately sends constructor calls.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ class NavigationDelegateProxyAPITests: XCTestCase {

XCTAssertEqual(api.didReceiveAuthenticationChallengeArgs, [webView, challenge])
XCTAssertEqual(dispositionResult, .useCredential)
XCTAssertNotNil(credentialResult)
XCTAssertEqual(credentialResult?.user, "user1")
XCTAssertEqual(credentialResult?.password, "password1")
XCTAssertEqual(credentialResult?.persistence, URLCredential.Persistence.none)
}
}

Expand Down Expand Up @@ -219,17 +221,17 @@ class TestNavigationDelegateApi: PigeonApiProtocolWKNavigationDelegate {
challenge challengeArg: URLAuthenticationChallenge,
completion: @escaping (
Result<
webview_flutter_wkwebview.AuthenticationChallengeResponse,
[Any?],
webview_flutter_wkwebview.PigeonError
>
) -> Void
) {
didReceiveAuthenticationChallengeArgs = [webViewArg, challengeArg]
completion(
.success(
AuthenticationChallengeResponse(
disposition: .useCredential,
credential: URLCredential(user: "user", password: "password", persistence: .none))))
.success([
UrlSessionAuthChallengeDisposition.useCredential,
["user": "user1", "password": "password1", "persistence": UrlCredentialPersistence.none],
]))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,41 @@ public class NavigationDelegateImpl: NSObject, WKNavigationDelegate {
DispatchQueue.main.async {
switch result {
case .success(let response):
completionHandler(response.disposition, response.credential)
let disposition = response[0] as! UrlSessionAuthChallengeDisposition
var nativeDisposition: URLSession.AuthChallengeDisposition
switch disposition {
case .useCredential:
nativeDisposition = .useCredential
case .performDefaultHandling:
nativeDisposition = .performDefaultHandling
case .cancelAuthenticationChallenge:
nativeDisposition = .cancelAuthenticationChallenge
case .rejectProtectionSpace:
nativeDisposition = .rejectProtectionSpace
case .unknown:
print(
self.registrar.createUnknownEnumError(withEnum: disposition).localizedDescription)
nativeDisposition = .cancelAuthenticationChallenge
}
let credentialMap = response[1] as? [AnyHashable?: AnyHashable?]
var credential: URLCredential?
if let credentialMap = credentialMap {
let nativePersistence: URLCredential.Persistence
switch credentialMap["persistence"] as! UrlCredentialPersistence {
case .none:
nativePersistence = .none
case .forSession:
nativePersistence = .forSession
case .permanent:
nativePersistence = .permanent
case .synchronizable:
nativePersistence = .synchronizable
}
credential = URLCredential(
user: credentialMap["user"] as! String,
password: credentialMap["password"] as! String, persistence: nativePersistence)
}
completionHandler(nativeDisposition, credential)
case .failure(let error):
completionHandler(.cancelAuthenticationChallenge, nil)
onFailure("WKNavigationDelegate.didReceiveAuthenticationChallenge", error)
Expand All @@ -266,7 +300,41 @@ public class NavigationDelegateImpl: NSObject, WKNavigationDelegate {
DispatchQueue.main.async {
switch result {
case .success(let response):
completionHandler(response.disposition, response.credential)
let disposition = response[0] as! UrlSessionAuthChallengeDisposition
var nativeDisposition: URLSession.AuthChallengeDisposition
switch disposition {
case .useCredential:
nativeDisposition = .useCredential
case .performDefaultHandling:
nativeDisposition = .performDefaultHandling
case .cancelAuthenticationChallenge:
nativeDisposition = .cancelAuthenticationChallenge
case .rejectProtectionSpace:
nativeDisposition = .rejectProtectionSpace
case .unknown:
print(
self.registrar.createUnknownEnumError(withEnum: disposition).localizedDescription)
nativeDisposition = .cancelAuthenticationChallenge
}
let credentialMap = response[1] as? [AnyHashable?: AnyHashable?]
var credential: URLCredential?
if let credentialMap = credentialMap {
let nativePersistence: URLCredential.Persistence
switch credentialMap["persistence"] as! UrlCredentialPersistence {
case .none:
nativePersistence = .none
case .forSession:
nativePersistence = .forSession
case .permanent:
nativePersistence = .permanent
case .synchronizable:
nativePersistence = .synchronizable
}
credential = URLCredential(
user: credentialMap["user"] as! String,
password: credentialMap["password"] as! String, persistence: nativePersistence)
}
completionHandler(nativeDisposition, credential)
case .failure(let error):
completionHandler(.cancelAuthenticationChallenge, nil)
onFailure("WKNavigationDelegate.didReceiveAuthenticationChallenge", error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3992,10 +3992,13 @@ protocol PigeonApiProtocolWKNavigationDelegate {
pigeonInstance pigeonInstanceArg: WKNavigationDelegate, webView webViewArg: WKWebView,
completion: @escaping (Result<Void, PigeonError>) -> Void)
/// Asks the delegate to respond to an authentication challenge.
///
/// Returns a List with a `UrlSessionAuthChallengeDisposition` and a nullable
/// `URLCredential`.
func didReceiveAuthenticationChallenge(
pigeonInstance pigeonInstanceArg: WKNavigationDelegate, webView webViewArg: WKWebView,
challenge challengeArg: URLAuthenticationChallenge,
completion: @escaping (Result<AuthenticationChallengeResponse, PigeonError>) -> Void)
completion: @escaping (Result<[Any?], PigeonError>) -> Void)
}

final class PigeonApiWKNavigationDelegate: PigeonApiProtocolWKNavigationDelegate {
Expand Down Expand Up @@ -4333,10 +4336,13 @@ final class PigeonApiWKNavigationDelegate: PigeonApiProtocolWKNavigationDelegate
}

/// Asks the delegate to respond to an authentication challenge.
///
/// Returns a List with a `UrlSessionAuthChallengeDisposition` and a nullable
/// `URLCredential`.
func didReceiveAuthenticationChallenge(
pigeonInstance pigeonInstanceArg: WKNavigationDelegate, webView webViewArg: WKWebView,
challenge challengeArg: URLAuthenticationChallenge,
completion: @escaping (Result<AuthenticationChallengeResponse, PigeonError>) -> Void
completion: @escaping (Result<[Any?], PigeonError>) -> Void
) {
if pigeonRegistrar.ignoreCallsToDart {
completion(
Expand Down Expand Up @@ -4369,7 +4375,7 @@ final class PigeonApiWKNavigationDelegate: PigeonApiProtocolWKNavigationDelegate
code: "null-error",
message: "Flutter api returned null value for non-null return value.", details: "")))
} else {
let result = listResponse[0] as! AuthenticationChallengeResponse
let result = listResponse[0] as! [Any?]
completion(.success(result))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4326,6 +4326,9 @@ class WKNavigationDelegate extends NSObject {

/// Asks the delegate to respond to an authentication challenge.
///
/// Returns a List with a `UrlSessionAuthChallengeDisposition` and a nullable
/// `URLCredential`.
///
/// For the associated Native object to be automatically garbage collected,
/// it is required that the implementation of this `Function` doesn't have a
/// strong reference to the encapsulating class instance. When this `Function`
Expand All @@ -4343,7 +4346,7 @@ class WKNavigationDelegate extends NSObject {
///
/// Alternatively, [PigeonInstanceManager.removeWeakReference] can be used to
/// release the associated Native object manually.
final Future<AuthenticationChallengeResponse> Function(
final Future<List<Object?>> Function(
WKNavigationDelegate pigeon_instance,
WKWebView webView,
URLAuthenticationChallenge challenge,
Expand Down Expand Up @@ -4387,7 +4390,7 @@ class WKNavigationDelegate extends NSObject {
WKNavigationDelegate pigeon_instance,
WKWebView webView,
)? webViewWebContentProcessDidTerminate,
Future<AuthenticationChallengeResponse> Function(
Future<List<Object?>> Function(
WKNavigationDelegate pigeon_instance,
WKWebView webView,
URLAuthenticationChallenge challenge,
Expand Down Expand Up @@ -4693,7 +4696,7 @@ class WKNavigationDelegate extends NSObject {
assert(arg_challenge != null,
'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKNavigationDelegate.didReceiveAuthenticationChallenge was null, expected non-null URLAuthenticationChallenge.');
try {
final AuthenticationChallengeResponse output =
final List<Object?> output =
await (didReceiveAuthenticationChallenge ??
arg_pigeon_instance!.didReceiveAuthenticationChallenge)
.call(arg_pigeon_instance!, arg_webView!, arg_challenge!);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ class WebKitWebViewPlatformController extends WebViewPlatformController {
return NavigationResponsePolicy.allow;
},
didReceiveAuthenticationChallenge: (_, __, ___) async {
return AuthenticationChallengeResponse(
disposition:
UrlSessionAuthChallengeDisposition.performDefaultHandling,
);
return <Object?>[
UrlSessionAuthChallengeDisposition.performDefaultHandling,
null,
];
},
);
},
Expand Down Expand Up @@ -751,7 +751,7 @@ class WebViewWidgetProxy {
WKWebView webView,
WKNavigationResponse navigationResponse,
) decidePolicyForNavigationResponse,
required Future<AuthenticationChallengeResponse> Function(
required Future<List<Object?>> Function(
WKNavigationDelegate,
WKWebView webView,
URLAuthenticationChallenge challenge,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class WebKitProxy {
WKNavigationDelegate,
WKWebView,
)? webViewWebContentProcessDidTerminate,
required Future<AuthenticationChallengeResponse> Function(
required Future<List<Object?>> Function(
WKNavigationDelegate,
WKWebView,
URLAuthenticationChallenge,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1213,43 +1213,44 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate {
final String host = protectionSpace.host;
final String? realm = protectionSpace.realm;

final Completer<AuthenticationChallengeResponse> responseCompleter =
Completer<AuthenticationChallengeResponse>();
final Completer<List<Object?>> responseCompleter =
Completer<List<Object?>>();

callback(
HttpAuthRequest(
host: host,
realm: realm,
onProceed: (WebViewCredential credential) {
final AuthenticationChallengeResponse response =
proxy.newAuthenticationChallengeResponse(
disposition: UrlSessionAuthChallengeDisposition.useCredential,
credential: URLCredential.withUser(
user: credential.user,
password: credential.password,
persistence: UrlCredentialPersistence.forSession,
),
responseCompleter.complete(
<Object?>[
UrlSessionAuthChallengeDisposition.useCredential,
<String, Object?>{
'user': credential.user,
'password': credential.password,
'persistence': UrlCredentialPersistence.forSession,
},
],
);
responseCompleter.complete(response);
},
onCancel: () {
final AuthenticationChallengeResponse response =
proxy.newAuthenticationChallengeResponse(
disposition: UrlSessionAuthChallengeDisposition
.cancelAuthenticationChallenge,
responseCompleter.complete(
<Object?>[
UrlSessionAuthChallengeDisposition
.cancelAuthenticationChallenge,
null,
],
);
responseCompleter.complete(response);
},
),
);

return responseCompleter.future;
}

return AuthenticationChallengeResponse(
disposition:
UrlSessionAuthChallengeDisposition.performDefaultHandling,
);
return <Object?>[
UrlSessionAuthChallengeDisposition.performDefaultHandling,
null,
];
},
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -745,9 +745,15 @@ abstract class WKNavigationDelegate extends NSObject {
/// Tells the delegate that the web view’s content process was terminated.
void Function(WKWebView webView)? webViewWebContentProcessDidTerminate;

// TODO(bparrishMines): This method should return an
// `AuthenticationChallengeResponse` once the cause of
// https://github.com/flutter/flutter/issues/162437 can be found and fixed.
/// Asks the delegate to respond to an authentication challenge.
///
/// This return value expects a List with a
/// `UrlSessionAuthChallengeDisposition` and a nullable `URLCredential`.
@async
late AuthenticationChallengeResponse Function(
late List<Object?> Function(
WKWebView webView,
URLAuthenticationChallenge challenge,
) didReceiveAuthenticationChallenge;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview
description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control.
repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_wkwebview
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
version: 3.18.2
version: 3.18.3

environment:
sdk: ^3.5.0
Expand Down
Loading