From 70e7861a7d345895cddc7680882725048268273c Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 14 Jan 2021 23:07:27 +0100 Subject: [PATCH] Implement authentication support for wxWebRequest under Mac Add wxWebAuthChallengeURLSession and use the appropriate delegate callback to create it. --- .../wx/osx/private/webrequest_urlsession.h | 35 ++++++- src/osx/webrequest_urlsession.mm | 99 ++++++++++++++++++- tests/net/webrequest.cpp | 18 ---- 3 files changed, 128 insertions(+), 24 deletions(-) diff --git a/include/wx/osx/private/webrequest_urlsession.h b/include/wx/osx/private/webrequest_urlsession.h index bea7502b96..4ecf0a6abf 100644 --- a/include/wx/osx/private/webrequest_urlsession.h +++ b/include/wx/osx/private/webrequest_urlsession.h @@ -14,6 +14,7 @@ #include "wx/private/webrequest.h" +DECLARE_WXCOCOA_OBJC_CLASS(NSURLCredential); DECLARE_WXCOCOA_OBJC_CLASS(NSURLSession); DECLARE_WXCOCOA_OBJC_CLASS(NSURLSessionTask); DECLARE_WXCOCOA_OBJC_CLASS(wxWebSessionDelegate); @@ -22,6 +23,29 @@ class wxWebSessionURLSession; class wxWebRequestURLSession; class wxWebResponseURLSession; +class wxWebAuthChallengeURLSession : public wxWebAuthChallengeImpl +{ +public: + wxWebAuthChallengeURLSession(wxWebAuthChallenge::Source source, + wxWebRequestURLSession& request) + : wxWebAuthChallengeImpl(source), + m_request(request) + { + } + + ~wxWebAuthChallengeURLSession(); + + void SetCredentials(const wxWebCredentials& cred) wxOVERRIDE; + + WX_NSURLCredential GetURLCredential() const { return m_cred; } + +private: + wxWebRequestURLSession& m_request; + WX_NSURLCredential m_cred = NULL; + + wxDECLARE_NO_COPY_CLASS(wxWebAuthChallengeURLSession); +}; + class wxWebResponseURLSession : public wxWebResponseImpl { public: @@ -67,7 +91,8 @@ public: wxWebResponseImplPtr GetResponse() const wxOVERRIDE { return m_response; } - wxWebAuthChallengeImplPtr GetAuthChallenge() const wxOVERRIDE; + wxWebAuthChallengeImplPtr GetAuthChallenge() const wxOVERRIDE + { return m_authChallenge; } wxFileOffset GetBytesSent() const wxOVERRIDE; @@ -79,14 +104,22 @@ public: void HandleCompletion(); + void HandleChallenge(wxWebAuthChallengeURLSession* challenge); + + void OnSetCredentials(const wxWebCredentials& cred); + wxWebResponseURLSession* GetResponseImplPtr() const { return m_response.get(); } + wxWebAuthChallengeURLSession* GetAuthChallengeImplPtr() const + { return m_authChallenge.get(); } + private: wxWebSessionURLSession& m_sessionImpl; wxString m_url; WX_NSURLSessionTask m_task; wxObjectDataPtr m_response; + wxObjectDataPtr m_authChallenge; wxDECLARE_NO_COPY_CLASS(wxWebRequestURLSession); }; diff --git a/src/osx/webrequest_urlsession.mm b/src/osx/webrequest_urlsession.mm index 5d7f517b13..18be4f1eb3 100644 --- a/src/osx/webrequest_urlsession.mm +++ b/src/osx/webrequest_urlsession.mm @@ -97,6 +97,60 @@ [m_requests removeObjectForKey:task]; } +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler +{ + wxUnusedVar(session); + + wxWebRequestURLSession* request = [self requestForTask:task]; + wxCHECK_RET( request, "received authentication challenge for an unknown task" ); + + NSURLProtectionSpace* const space = [challenge protectionSpace]; + + wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: didReceiveChallenge for %s", + request, + wxCFStringRefFromGet([space description]).AsString()); + + // We need to distinguish between session-wide and task-specific + // authentication challenges, we're only really interested in the latter + // ones here (but apparently there is no way to get just them, even though + // the documentation seems to imply that session-wide challenges shouldn't + // be sent to this task-specific delegate -- but they're, at least under + // 10.14). + const auto authMethod = space.authenticationMethod; + if ( authMethod == NSURLAuthenticationMethodHTTPBasic || + authMethod == NSURLAuthenticationMethodHTTPDigest ) + { + if ( auto* const authChallenge = request->GetAuthChallengeImplPtr() ) + { + // We're going to get called until we don't provide the correct + // credentials, so don't use them again (and again, and again...) + // if we had already used them unsuccessfully. + if ( !challenge.previousFailureCount ) + { + wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: using credentials", request); + + completionHandler(NSURLSessionAuthChallengeUseCredential, + authChallenge->GetURLCredential()); + + return; + } + + wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: not using failing credentials again", request); + } + + request->HandleChallenge(new wxWebAuthChallengeURLSession( + [space isProxy] ? wxWebAuthChallenge::Source_Proxy + : wxWebAuthChallenge::Source_Server, + *request + )); + } + + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); +} + @end // @@ -170,14 +224,22 @@ void wxWebRequestURLSession::Cancel() void wxWebRequestURLSession::HandleCompletion() { - if ( CheckServerStatus() ) - SetState(wxWebRequest::State_Completed); + switch ( m_response->GetStatus() ) + { + case 401: + case 407: + SetState(wxWebRequest::State_Unauthorized, m_response->GetStatusText()); + break; + + default: + if ( CheckServerStatus() ) + SetState(wxWebRequest::State_Completed); + } } -wxWebAuthChallengeImplPtr wxWebRequestURLSession::GetAuthChallenge() const +void wxWebRequestURLSession::HandleChallenge(wxWebAuthChallengeURLSession* challenge) { - wxFAIL_MSG("not implemented"); - return wxWebAuthChallengeImplPtr(); + m_authChallenge.reset(challenge); } wxFileOffset wxWebRequestURLSession::GetBytesSent() const @@ -200,6 +262,33 @@ wxFileOffset wxWebRequestURLSession::GetBytesExpectedToReceive() const return m_task.countOfBytesExpectedToReceive; } +// +// wxWebAuthChallengeURLSession +// + +wxWebAuthChallengeURLSession::~wxWebAuthChallengeURLSession() +{ + [m_cred release]; +} + +void wxWebAuthChallengeURLSession::SetCredentials(const wxWebCredentials& cred) +{ + wxLogTrace(wxTRACE_WEBREQUEST, "Request %p: setting credentials", &m_request); + + [m_cred release]; + + m_cred = [NSURLCredential + credentialWithUser:wxCFStringRef(cred.GetUser()).AsNSString() + password:wxCFStringRef(wxSecretString(cred.GetPassword())).AsNSString() + persistence:NSURLCredentialPersistenceNone + ]; + + [m_cred retain]; + + m_request.Start(); +} + + // // wxWebResponseURLSession // diff --git a/tests/net/webrequest.cpp b/tests/net/webrequest.cpp index 188a6ff0b0..8e54152600 100644 --- a/tests/net/webrequest.cpp +++ b/tests/net/webrequest.cpp @@ -42,12 +42,6 @@ public: dataSize = 0; } - static bool UsingNSURLSession() - { - return wxWebSession::GetDefault().GetLibraryVersionInfo().GetName() - == "URLSession"; - } - // All tests should call this function first and skip the test entirely if // it returns false, as this indicates that web requests tests are disabled. bool InitBaseURL() @@ -240,12 +234,6 @@ TEST_CASE_METHOD(RequestFixture, if ( !InitBaseURL() ) return; - if ( UsingNSURLSession() ) - { - WARN("NSURLSession backend doesn't support authentication, skipping."); - return; - } - Create("/basic-auth/wxtest/wxwidgets"); Run(wxWebRequest::State_Unauthorized, 401); REQUIRE( request.GetAuthChallenge().IsOk() ); @@ -273,12 +261,6 @@ TEST_CASE_METHOD(RequestFixture, if ( !InitBaseURL() ) return; - if ( UsingNSURLSession() ) - { - WARN("NSURLSession backend doesn't support authentication, skipping."); - return; - } - Create("/digest-auth/auth/wxtest/wxwidgets"); Run(wxWebRequest::State_Unauthorized, 401); REQUIRE( request.GetAuthChallenge().IsOk() );