1/*
2 * Copyright (C) 2011 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2,1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "LoadTrackingTest.h"
22#include "WebKitTestServer.h"
23#include <wtf/glib/GRefPtr.h>
24
25static WebKitTestServer* kServer;
26
27class AuthenticationTest: public LoadTrackingTest {
28public:
29 MAKE_GLIB_TEST_FIXTURE(AuthenticationTest);
30
31 AuthenticationTest()
32 {
33 g_signal_connect(m_webView, "authenticate", G_CALLBACK(runAuthenticationCallback), this);
34 }
35
36 ~AuthenticationTest()
37 {
38 g_signal_handlers_disconnect_matched(m_webView, G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, this);
39 }
40
41 static int authenticationRetries;
42 static bool authenticationCancelledReceived;
43
44 void loadURI(const char* uri)
45 {
46 // Reset the retry count of the fake server when a page is loaded.
47 authenticationRetries = 0;
48 authenticationCancelledReceived = false;
49 LoadTrackingTest::loadURI(uri);
50 }
51
52 static gboolean runAuthenticationCallback(WebKitWebView*, WebKitAuthenticationRequest* request, AuthenticationTest* test)
53 {
54 g_signal_connect(request, "cancelled", G_CALLBACK(authenticationCancelledCallback), test);
55 test->runAuthentication(request);
56 return TRUE;
57 }
58
59 static void authenticationCancelledCallback(WebKitAuthenticationRequest*, AuthenticationTest*)
60 {
61 authenticationCancelledReceived = true;
62 }
63
64 void runAuthentication(WebKitAuthenticationRequest* request)
65 {
66 assertObjectIsDeletedWhenTestFinishes(G_OBJECT(request));
67 m_authenticationRequest = request;
68 g_main_loop_quit(m_mainLoop);
69 }
70
71 WebKitAuthenticationRequest* waitForAuthenticationRequest()
72 {
73 g_main_loop_run(m_mainLoop);
74 return m_authenticationRequest.get();
75 }
76
77private:
78 GRefPtr<WebKitAuthenticationRequest> m_authenticationRequest;
79};
80
81int AuthenticationTest::authenticationRetries = 0;
82bool AuthenticationTest::authenticationCancelledReceived = false;
83
84static const char authTestUsername[] = "username";
85static const char authTestPassword[] = "password";
86static const char authExpectedSuccessTitle[] = "WebKit2Gtk+ Authentication test";
87static const char authExpectedFailureTitle[] = "401 Authorization Required";
88static const char authExpectedAuthorization[] = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="; // Base64 encoding of "username:password".
89static const char authSuccessHTMLString[] =
90 "<html>"
91 "<head><title>WebKit2Gtk+ Authentication test</title></head>"
92 "<body></body></html>";
93static const char authFailureHTMLString[] =
94 "<html>"
95 "<head><title>401 Authorization Required</title></head>"
96 "<body></body></html>";
97
98static void testWebViewAuthenticationRequest(AuthenticationTest* test, gconstpointer)
99{
100 // Test authentication request getters match soup authentication header.
101 test->loadURI(kServer->getURIForPath("/auth-test.html").data());
102 WebKitAuthenticationRequest* request = test->waitForAuthenticationRequest();
103 g_assert_cmpstr(webkit_authentication_request_get_host(request), ==, soup_uri_get_host(kServer->baseURI()));
104 g_assert_cmpuint(webkit_authentication_request_get_port(request), ==, soup_uri_get_port(kServer->baseURI()));
105 g_assert_cmpstr(webkit_authentication_request_get_realm(request), ==, "my realm");
106 g_assert_cmpint(webkit_authentication_request_get_scheme(request), ==, WEBKIT_AUTHENTICATION_SCHEME_HTTP_BASIC);
107 g_assert_false(webkit_authentication_request_is_for_proxy(request));
108 g_assert_false(webkit_authentication_request_is_retry(request));
109}
110
111static void testWebViewAuthenticationCancel(AuthenticationTest* test, gconstpointer)
112{
113 // Test cancel.
114 test->loadURI(kServer->getURIForPath("/auth-test.html").data());
115 WebKitAuthenticationRequest* request = test->waitForAuthenticationRequest();
116 webkit_authentication_request_cancel(request);
117 // Server doesn't ask for new credentials.
118 test->waitUntilLoadFinished();
119
120 g_assert_cmpint(test->m_loadEvents.size(), ==, 3);
121 g_assert_cmpint(test->m_loadEvents[0], ==, LoadTrackingTest::ProvisionalLoadStarted);
122 g_assert_cmpint(test->m_loadEvents[1], ==, LoadTrackingTest::ProvisionalLoadFailed);
123 g_assert_cmpint(test->m_loadEvents[2], ==, LoadTrackingTest::LoadFinished);
124
125 g_assert_error(test->m_error.get(), WEBKIT_NETWORK_ERROR, WEBKIT_NETWORK_ERROR_CANCELLED);
126}
127
128static void testWebViewAuthenticationLoadCancelled(AuthenticationTest* test, gconstpointer)
129{
130 test->loadURI(kServer->getURIForPath("/auth-test.html").data());
131 test->waitForAuthenticationRequest();
132 webkit_web_view_stop_loading(test->m_webView);
133 // Expect empty page.
134 test->waitUntilLoadFinished();
135 g_assert_true(test->authenticationCancelledReceived);
136
137 g_assert_cmpint(test->m_loadEvents.size(), ==, 3);
138 g_assert_cmpint(test->m_loadEvents[0], ==, LoadTrackingTest::ProvisionalLoadStarted);
139 g_assert_cmpint(test->m_loadEvents[1], ==, LoadTrackingTest::ProvisionalLoadFailed);
140 g_assert_cmpint(test->m_loadEvents[2], ==, LoadTrackingTest::LoadFinished);
141
142 g_assert_error(test->m_error.get(), WEBKIT_NETWORK_ERROR, WEBKIT_NETWORK_ERROR_CANCELLED);
143}
144
145static void testWebViewAuthenticationFailure(AuthenticationTest* test, gconstpointer)
146{
147 // Test authentication failures.
148 test->loadURI(kServer->getURIForPath("/auth-test.html").data());
149 WebKitAuthenticationRequest* request = test->waitForAuthenticationRequest();
150 g_assert_false(webkit_authentication_request_is_retry(request));
151 WebKitCredential* credential = webkit_credential_new(authTestUsername, "wrongpassword", WEBKIT_CREDENTIAL_PERSISTENCE_NONE);
152 webkit_authentication_request_authenticate(request, credential);
153 webkit_credential_free(credential);
154 // Expect a second authentication request.
155 request = test->waitForAuthenticationRequest();
156 g_assert_true(webkit_authentication_request_is_retry(request));
157 // Test second failure.
158 credential = webkit_credential_new(authTestUsername, "wrongpassword2", WEBKIT_CREDENTIAL_PERSISTENCE_NONE);
159 webkit_authentication_request_authenticate(request, credential);
160 webkit_credential_free(credential);
161 // Expect authentication failed page.
162 test->waitUntilLoadFinished();
163
164 g_assert_cmpint(test->m_loadEvents.size(), ==, 3);
165 g_assert_cmpint(test->m_loadEvents[0], ==, LoadTrackingTest::ProvisionalLoadStarted);
166 g_assert_cmpint(test->m_loadEvents[1], ==, LoadTrackingTest::LoadCommitted);
167 g_assert_cmpint(test->m_loadEvents[2], ==, LoadTrackingTest::LoadFinished);
168 g_assert_cmpstr(webkit_web_view_get_title(test->m_webView), ==, authExpectedFailureTitle);
169}
170
171static void testWebViewAuthenticationNoCredential(AuthenticationTest* test, gconstpointer)
172{
173 // Test continue without credentials.
174 test->loadURI(kServer->getURIForPath("/auth-test.html").data());
175 WebKitAuthenticationRequest* request = test->waitForAuthenticationRequest();
176 webkit_authentication_request_authenticate(request, 0);
177 // Server doesn't ask for new credentials.
178 test->waitUntilLoadFinished();
179
180 g_assert_cmpint(test->m_loadEvents.size(), ==, 3);
181 g_assert_cmpint(test->m_loadEvents[0], ==, LoadTrackingTest::ProvisionalLoadStarted);
182 g_assert_cmpint(test->m_loadEvents[1], ==, LoadTrackingTest::LoadCommitted);
183 g_assert_cmpint(test->m_loadEvents[2], ==, LoadTrackingTest::LoadFinished);
184 g_assert_cmpstr(webkit_web_view_get_title(test->m_webView), ==, authExpectedFailureTitle);
185}
186
187// FIXME: Find a way to not use the private browsing setting and enable for WPE.
188#if PLATFORM(GTK)
189static void testWebViewAuthenticationStorage(AuthenticationTest* test, gconstpointer)
190{
191 // Enable private browsing before authentication request to test that credentials can't be saved.
192 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
193 webkit_settings_set_enable_private_browsing(webkit_web_view_get_settings(test->m_webView), TRUE);
194 G_GNUC_END_IGNORE_DEPRECATIONS;
195 test->loadURI(kServer->getURIForPath("/auth-test.html").data());
196 WebKitAuthenticationRequest* request = test->waitForAuthenticationRequest();
197 g_assert_null(webkit_authentication_request_get_proposed_credential(request));
198 g_assert_false(webkit_authentication_request_can_save_credentials(request));
199
200 // If WebKit has been compiled with libsecret, and private browsing is disabled
201 // then check that credentials can be saved.
202#if USE(LIBSECRET)
203 G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
204 webkit_settings_set_enable_private_browsing(webkit_web_view_get_settings(test->m_webView), FALSE);
205 G_GNUC_END_IGNORE_DEPRECATIONS;
206 test->loadURI(kServer->getURIForPath("/auth-test.html").data());
207 request = test->waitForAuthenticationRequest();
208 g_assert_null(webkit_authentication_request_get_proposed_credential(request));
209 g_assert_true(webkit_authentication_request_can_save_credentials(request));
210#endif
211}
212#endif
213
214static void testWebViewAuthenticationSuccess(AuthenticationTest* test, gconstpointer)
215{
216 // Test correct authentication.
217 test->loadURI(kServer->getURIForPath("/auth-test.html").data());
218 WebKitAuthenticationRequest* request = test->waitForAuthenticationRequest();
219 WebKitCredential* credential = webkit_credential_new(authTestUsername, authTestPassword, WEBKIT_CREDENTIAL_PERSISTENCE_FOR_SESSION);
220 webkit_authentication_request_authenticate(request, credential);
221 webkit_credential_free(credential);
222 test->waitUntilLoadFinished();
223
224 g_assert_cmpint(test->m_loadEvents.size(), ==, 3);
225 g_assert_cmpint(test->m_loadEvents[0], ==, LoadTrackingTest::ProvisionalLoadStarted);
226 g_assert_cmpint(test->m_loadEvents[1], ==, LoadTrackingTest::LoadCommitted);
227 g_assert_cmpint(test->m_loadEvents[2], ==, LoadTrackingTest::LoadFinished);
228 g_assert_cmpstr(webkit_web_view_get_title(test->m_webView), ==, authExpectedSuccessTitle);
229
230 // Test loading the same (authorized) page again.
231 test->loadURI(kServer->getURIForPath("/auth-test.html").data());
232 // There is no authentication challenge.
233 test->waitUntilLoadFinished();
234
235 g_assert_cmpint(test->m_loadEvents.size(), ==, 3);
236 g_assert_cmpint(test->m_loadEvents[0], ==, LoadTrackingTest::ProvisionalLoadStarted);
237 g_assert_cmpint(test->m_loadEvents[1], ==, LoadTrackingTest::LoadCommitted);
238 g_assert_cmpint(test->m_loadEvents[2], ==, LoadTrackingTest::LoadFinished);
239 g_assert_cmpstr(webkit_web_view_get_title(test->m_webView), ==, authExpectedSuccessTitle);
240}
241
242static void testWebViewAuthenticationEmptyRealm(AuthenticationTest* test, gconstpointer)
243{
244 test->loadURI(kServer->getURIForPath("/empty-realm.html").data());
245 WebKitAuthenticationRequest* request = test->waitForAuthenticationRequest();
246 WebKitCredential* credential = webkit_credential_new(authTestUsername, authTestPassword, WEBKIT_CREDENTIAL_PERSISTENCE_FOR_SESSION);
247 webkit_authentication_request_authenticate(request, credential);
248 webkit_credential_free(credential);
249 test->waitUntilLoadFinished();
250
251 g_assert_cmpint(test->m_loadEvents.size(), ==, 3);
252 g_assert_cmpint(test->m_loadEvents[0], ==, LoadTrackingTest::ProvisionalLoadStarted);
253 g_assert_cmpint(test->m_loadEvents[1], ==, LoadTrackingTest::LoadCommitted);
254 g_assert_cmpint(test->m_loadEvents[2], ==, LoadTrackingTest::LoadFinished);
255 g_assert_cmpstr(webkit_web_view_get_title(test->m_webView), ==, authExpectedSuccessTitle);
256}
257
258class Tunnel {
259public:
260 Tunnel(SoupServer* server, SoupMessage* message)
261 : m_server(server)
262 , m_message(message)
263 {
264 soup_server_pause_message(m_server.get(), m_message.get());
265 }
266
267 ~Tunnel()
268 {
269 soup_server_unpause_message(m_server.get(), m_message.get());
270 }
271
272 void connect(Function<void (const char*)>&& completionHandler)
273 {
274 m_completionHandler = WTFMove(completionHandler);
275 GRefPtr<GSocketClient> client = adoptGRef(g_socket_client_new());
276 auto* uri = soup_message_get_uri(m_message.get());
277 g_socket_client_connect_to_host_async(client.get(), uri->host, uri->port, nullptr, [](GObject* source, GAsyncResult* result, gpointer userData) {
278 auto* tunnel = static_cast<Tunnel*>(userData);
279 GUniqueOutPtr<GError> error;
280 GRefPtr<GSocketConnection> connection = adoptGRef(g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source), result, &error.outPtr()));
281 tunnel->connected(!connection ? error->message : nullptr);
282 }, this);
283 }
284
285 void connected(const char* errorMessage)
286 {
287 auto completionHandler = std::exchange(m_completionHandler, nullptr);
288 completionHandler(errorMessage);
289 }
290
291 GRefPtr<SoupServer> m_server;
292 GRefPtr<SoupMessage> m_message;
293 Function<void (const char*)> m_completionHandler;
294};
295
296unsigned gProxyServerPort;
297
298static void serverCallback(SoupServer* server, SoupMessage* message, const char* path, GHashTable*, SoupClientContext* context, void*)
299{
300 if (message->method == SOUP_METHOD_CONNECT) {
301 g_assert_cmpuint(soup_server_get_port(server), ==, gProxyServerPort);
302 auto tunnel = std::make_unique<Tunnel>(server, message);
303 auto* tunnelPtr = tunnel.get();
304 tunnelPtr->connect([tunnel = WTFMove(tunnel)](const char* errorMessage) {
305 if (errorMessage) {
306 soup_message_set_status(tunnel->m_message.get(), SOUP_STATUS_BAD_GATEWAY);
307 soup_message_set_response(tunnel->m_message.get(), "text/plain", SOUP_MEMORY_COPY, errorMessage, strlen(errorMessage));
308 } else {
309 soup_message_headers_append(tunnel->m_message->response_headers, "Proxy-Authenticate", "Basic realm=\"Proxy realm\"");
310 soup_message_set_status(tunnel->m_message.get(), SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED);
311 }
312 });
313 return;
314 }
315
316 if (message->method != SOUP_METHOD_GET) {
317 soup_message_set_status(message, SOUP_STATUS_NOT_IMPLEMENTED);
318 return;
319 }
320
321 if (g_str_has_suffix(path, "/auth-test.html") || g_str_has_suffix(path, "/empty-realm.html")) {
322 bool isProxy = g_str_has_prefix(path, "/proxy");
323 if (isProxy)
324 g_assert_cmpuint(soup_server_get_port(server), ==, gProxyServerPort);
325
326 const char* authorization = soup_message_headers_get_one(message->request_headers, "Authorization");
327 // Require authentication.
328 if (!g_strcmp0(authorization, authExpectedAuthorization)) {
329 // Successful authentication.
330 soup_message_set_status(message, SOUP_STATUS_OK);
331 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, authSuccessHTMLString, strlen(authSuccessHTMLString));
332 AuthenticationTest::authenticationRetries = 0;
333 } else if (++AuthenticationTest::authenticationRetries < 3) {
334 // No or invalid authorization header provided by the client, request authentication twice then fail.
335 soup_message_set_status(message, isProxy ? SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED : SOUP_STATUS_UNAUTHORIZED);
336 if (!strcmp(path, "/empty-realm.html"))
337 soup_message_headers_append(message->response_headers, "WWW-Authenticate", "Basic");
338 else
339 soup_message_headers_append(message->response_headers, isProxy ? "Proxy-Authenticate" : "WWW-Authenticate", isProxy ? "Basic realm=\"Proxy realm\"" : "Basic realm=\"my realm\"");
340 // Include a failure message in case the user attempts to proceed without authentication.
341 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, authFailureHTMLString, strlen(authFailureHTMLString));
342 } else {
343 // Authentication not successful, display a "401 Authorization Required" page.
344 soup_message_set_status(message, SOUP_STATUS_OK);
345 soup_message_body_append(message->response_body, SOUP_MEMORY_STATIC, authFailureHTMLString, strlen(authFailureHTMLString));
346 }
347 } else
348 soup_message_set_status(message, SOUP_STATUS_NOT_FOUND);
349
350 soup_message_body_complete(message->response_body);
351}
352
353class ProxyAuthenticationTest : public AuthenticationTest {
354public:
355 MAKE_GLIB_TEST_FIXTURE(ProxyAuthenticationTest);
356
357 ProxyAuthenticationTest()
358 {
359 m_proxyServer.run(serverCallback);
360 g_assert_nonnull(m_proxyServer.baseURI());
361 gProxyServerPort = soup_uri_get_port(m_proxyServer.baseURI());
362 GUniquePtr<char> proxyURI(soup_uri_to_string(m_proxyServer.baseURI(), FALSE));
363 WebKitNetworkProxySettings* settings = webkit_network_proxy_settings_new(proxyURI.get(), nullptr);
364 webkit_web_context_set_network_proxy_settings(m_webContext.get(), WEBKIT_NETWORK_PROXY_MODE_CUSTOM, settings);
365 webkit_network_proxy_settings_free(settings);
366 }
367
368 ~ProxyAuthenticationTest()
369 {
370 gProxyServerPort = 0;
371 }
372
373 GUniquePtr<char> proxyServerPortAsString()
374 {
375 GUniquePtr<char> port(g_strdup_printf("%u", soup_uri_get_port(m_proxyServer.baseURI())));
376 return port;
377 }
378
379 WebKitTestServer m_proxyServer;
380};
381
382static void testWebViewAuthenticationProxy(ProxyAuthenticationTest* test, gconstpointer)
383{
384 test->loadURI(kServer->getURIForPath("/proxy/auth-test.html").data());
385 WebKitAuthenticationRequest* request = test->waitForAuthenticationRequest();
386 // FIXME: the uri and host should the proxy ones, not the requested ones.
387 g_assert_cmpstr(webkit_authentication_request_get_host(request), ==, soup_uri_get_host(kServer->baseURI()));
388 g_assert_cmpuint(webkit_authentication_request_get_port(request), ==, soup_uri_get_port(kServer->baseURI()));
389 g_assert_cmpstr(webkit_authentication_request_get_realm(request), ==, "Proxy realm");
390 g_assert_cmpint(webkit_authentication_request_get_scheme(request), ==, WEBKIT_AUTHENTICATION_SCHEME_HTTP_BASIC);
391 g_assert_true(webkit_authentication_request_is_for_proxy(request));
392 g_assert_false(webkit_authentication_request_is_retry(request));
393}
394
395static void testWebViewAuthenticationProxyHTTPS(ProxyAuthenticationTest* test, gconstpointer)
396{
397 auto httpsServer = std::make_unique<WebKitTestServer>(WebKitTestServer::ServerHTTPS);
398 httpsServer->run(serverCallback);
399
400 test->loadURI(httpsServer->getURIForPath("/proxy/auth-test.html").data());
401 WebKitAuthenticationRequest* request = test->waitForAuthenticationRequest();
402 // FIXME: the uri and host should the proxy ones, not the requested ones.
403 g_assert_cmpstr(webkit_authentication_request_get_host(request), ==, soup_uri_get_host(httpsServer->baseURI()));
404 g_assert_cmpuint(webkit_authentication_request_get_port(request), ==, soup_uri_get_port(httpsServer->baseURI()));
405 g_assert_cmpstr(webkit_authentication_request_get_realm(request), ==, "Proxy realm");
406 g_assert_cmpint(webkit_authentication_request_get_scheme(request), ==, WEBKIT_AUTHENTICATION_SCHEME_HTTP_BASIC);
407 g_assert_true(webkit_authentication_request_is_for_proxy(request));
408 g_assert_false(webkit_authentication_request_is_retry(request));
409}
410
411void beforeAll()
412{
413 kServer = new WebKitTestServer();
414 kServer->run(serverCallback);
415
416 AuthenticationTest::add("Authentication", "authentication-request", testWebViewAuthenticationRequest);
417 AuthenticationTest::add("Authentication", "authentication-cancel", testWebViewAuthenticationCancel);
418 AuthenticationTest::add("Authentication", "authentication-load-cancelled", testWebViewAuthenticationLoadCancelled);
419 AuthenticationTest::add("Authentication", "authentication-success", testWebViewAuthenticationSuccess);
420 AuthenticationTest::add("Authentication", "authentication-failure", testWebViewAuthenticationFailure);
421 AuthenticationTest::add("Authentication", "authentication-no-credential", testWebViewAuthenticationNoCredential);
422#if PLATFORM(GTK)
423 AuthenticationTest::add("Authentication", "authentication-storage", testWebViewAuthenticationStorage);
424#endif
425 AuthenticationTest::add("Authentication", "authentication-empty-realm", testWebViewAuthenticationEmptyRealm);
426 ProxyAuthenticationTest::add("Authentication", "authentication-proxy", testWebViewAuthenticationProxy);
427 ProxyAuthenticationTest::add("Authentication", "authentication-proxy-https", testWebViewAuthenticationProxyHTTPS);
428}
429
430void afterAll()
431{
432 delete kServer;
433}
434